@powerportalspro/react-powerbi 0.0.0-stage → 13.0.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/EULA.txt +263 -0
- package/README.md +26 -2
- package/dist/index.cjs +712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +117 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +710 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { makeStyles, tokens, MessageBar, MessageBarBody, MessageBarActions, Button, Spinner, mergeClasses } from '@fluentui/react-components';
|
|
3
|
+
import { PowerBIConnectionRequiredError, PowerPortalsProError } from '@powerportalspro/core';
|
|
4
|
+
import { usePowerPortalsPro, useLocale, usePrefixedT } from '@powerportalspro/react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/power-bi-report.tsx
|
|
8
|
+
|
|
9
|
+
// src/core/portal-theme.ts
|
|
10
|
+
var PORTAL_SERIES_COLORS = [
|
|
11
|
+
"#d13438",
|
|
12
|
+
// red
|
|
13
|
+
"#107c10",
|
|
14
|
+
// green
|
|
15
|
+
"#ffb900",
|
|
16
|
+
// yellow
|
|
17
|
+
"#5c2d91",
|
|
18
|
+
// purple
|
|
19
|
+
"#008272",
|
|
20
|
+
// teal
|
|
21
|
+
"#d83b01",
|
|
22
|
+
// orange
|
|
23
|
+
"#e3008c",
|
|
24
|
+
// pink / magenta
|
|
25
|
+
"#4f6bed",
|
|
26
|
+
// cornflower
|
|
27
|
+
"#498205",
|
|
28
|
+
// forest
|
|
29
|
+
"#00cc6a",
|
|
30
|
+
// seafoam
|
|
31
|
+
"#7160e8",
|
|
32
|
+
// lavender
|
|
33
|
+
"#8e562e",
|
|
34
|
+
// brown
|
|
35
|
+
"#a4262c",
|
|
36
|
+
// burgundy
|
|
37
|
+
"#3a96dd",
|
|
38
|
+
// lightBlue
|
|
39
|
+
"#0099bc"
|
|
40
|
+
// cyan
|
|
41
|
+
];
|
|
42
|
+
function solid(color) {
|
|
43
|
+
return { solid: { color } };
|
|
44
|
+
}
|
|
45
|
+
function channels(color) {
|
|
46
|
+
return [1, 3, 5].map((start) => parseInt(color.slice(start, start + 2), 16));
|
|
47
|
+
}
|
|
48
|
+
function raisedSurface(colors) {
|
|
49
|
+
const surface = channels(colors.surface);
|
|
50
|
+
const foreground = channels(colors.foreground);
|
|
51
|
+
const brightness = ([red, green, blue]) => red * 0.299 + green * 0.587 + blue * 0.114;
|
|
52
|
+
if (brightness(surface) >= brightness(foreground)) return colors.surface;
|
|
53
|
+
const raised = surface.map((channel, index) => Math.round(channel + (foreground[index] - channel) * 0.15));
|
|
54
|
+
return `#${raised.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
55
|
+
}
|
|
56
|
+
function portalTheme(colors, options = {}) {
|
|
57
|
+
const text = (color) => ({ color });
|
|
58
|
+
const backdrop = options.transparent ? 100 : 0;
|
|
59
|
+
const tooltip = raisedSurface(colors);
|
|
60
|
+
const axis = {
|
|
61
|
+
labelColor: solid(colors.foregroundSecondary),
|
|
62
|
+
titleColor: solid(colors.foregroundSecondary),
|
|
63
|
+
gridlineColor: solid(colors.strokeSubtle)
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
name: "Portal",
|
|
67
|
+
dataColors: [colors.brand, ...PORTAL_SERIES_COLORS],
|
|
68
|
+
firstLevelElements: colors.foreground,
|
|
69
|
+
secondLevelElements: colors.foregroundSecondary,
|
|
70
|
+
thirdLevelElements: colors.strokeSubtle,
|
|
71
|
+
fourthLevelElements: colors.foregroundTertiary,
|
|
72
|
+
background: colors.surface,
|
|
73
|
+
secondaryBackground: colors.stroke,
|
|
74
|
+
tableAccent: colors.brand,
|
|
75
|
+
hyperlink: colors.link,
|
|
76
|
+
visitedHyperlink: colors.link,
|
|
77
|
+
good: colors.good,
|
|
78
|
+
neutral: colors.neutral,
|
|
79
|
+
bad: colors.bad,
|
|
80
|
+
textClasses: {
|
|
81
|
+
callout: text(colors.foreground),
|
|
82
|
+
title: text(colors.foreground),
|
|
83
|
+
header: text(colors.foreground),
|
|
84
|
+
label: text(colors.foreground),
|
|
85
|
+
// Legends, data labels and category labels, which the base theme draws in a light gray.
|
|
86
|
+
lightLabel: text(colors.foregroundSecondary),
|
|
87
|
+
smallLightLabel: text(colors.foregroundSecondary),
|
|
88
|
+
largeLightLabel: text(colors.foregroundSecondary)
|
|
89
|
+
},
|
|
90
|
+
visualStyles: {
|
|
91
|
+
"*": {
|
|
92
|
+
"*": {
|
|
93
|
+
background: [{ show: true, color: solid(colors.surface), transparency: backdrop }],
|
|
94
|
+
categoryAxis: [axis],
|
|
95
|
+
valueAxis: [axis],
|
|
96
|
+
// Tooltips are on unless the report turned them off, so colors alone switch nothing on.
|
|
97
|
+
// Each color twice: the classic tooltip reads the plain one, the modern one the themed.
|
|
98
|
+
visualTooltip: [
|
|
99
|
+
{
|
|
100
|
+
background: solid(tooltip),
|
|
101
|
+
themedBackground: solid(tooltip),
|
|
102
|
+
titleFontColor: solid(colors.foregroundSecondary),
|
|
103
|
+
themedTitleFontColor: solid(colors.foregroundSecondary),
|
|
104
|
+
valueFontColor: solid(colors.foreground),
|
|
105
|
+
themedValueFontColor: solid(colors.foreground)
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
card: {
|
|
111
|
+
"*": {
|
|
112
|
+
labels: [{ color: solid(colors.foreground) }],
|
|
113
|
+
categoryLabels: [{ color: solid(colors.foregroundSecondary) }]
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
// Detail labels sit beside the slices, on the page, rather than on them.
|
|
117
|
+
donutChart: { "*": { labels: [{ color: solid(colors.foregroundSecondary) }] } },
|
|
118
|
+
pieChart: { "*": { labels: [{ color: solid(colors.foregroundSecondary) }] } },
|
|
119
|
+
// A funnel names its stages through its category axis's own color, not an axis label color.
|
|
120
|
+
funnel: {
|
|
121
|
+
"*": {
|
|
122
|
+
categoryAxis: [{ color: solid(colors.foregroundSecondary) }],
|
|
123
|
+
percentBarLabel: [{ color: solid(colors.foregroundSecondary) }]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
// Tables and matrices: their text and gridlines, and nothing behind the cells, which show the
|
|
127
|
+
// visual's own background as they do by default. The lines under the headers and over the
|
|
128
|
+
// total stay the accent, from `tableAccent`.
|
|
129
|
+
tableEx: {
|
|
130
|
+
"*": {
|
|
131
|
+
columnHeaders: [{ fontColor: solid(colors.foreground) }],
|
|
132
|
+
values: [{ fontColorPrimary: solid(colors.foreground), fontColorSecondary: solid(colors.foreground) }],
|
|
133
|
+
total: [{ fontColor: solid(colors.foreground) }],
|
|
134
|
+
grid: [{ gridHorizontalColor: solid(colors.strokeSubtle), gridVerticalColor: solid(colors.strokeSubtle) }]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
pivotTable: {
|
|
138
|
+
"*": {
|
|
139
|
+
columnHeaders: [{ fontColor: solid(colors.foreground) }],
|
|
140
|
+
rowHeaders: [
|
|
141
|
+
{ fontColor: solid(colors.foreground), expandCollapseButtonsColor: solid(colors.foregroundSecondary) }
|
|
142
|
+
],
|
|
143
|
+
values: [{ fontColorPrimary: solid(colors.foreground), fontColorSecondary: solid(colors.foreground) }],
|
|
144
|
+
total: [{ fontColor: solid(colors.foreground) }],
|
|
145
|
+
subTotals: [{ fontColor: solid(colors.foreground) }],
|
|
146
|
+
columnTotal: [{ fontColor: solid(colors.foreground) }],
|
|
147
|
+
rowTotal: [{ fontColor: solid(colors.foreground) }],
|
|
148
|
+
grid: [{ gridHorizontalColor: solid(colors.strokeSubtle), gridVerticalColor: solid(colors.strokeSubtle) }]
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
slicer: {
|
|
152
|
+
"*": {
|
|
153
|
+
header: [{ fontColor: solid(colors.foreground) }],
|
|
154
|
+
items: [{ fontColor: solid(colors.foreground) }],
|
|
155
|
+
date: [{ fontColor: solid(colors.foreground) }],
|
|
156
|
+
numericInputStyle: [{ fontColor: solid(colors.foreground) }]
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
multiRowCard: {
|
|
160
|
+
"*": {
|
|
161
|
+
cardTitle: [{ color: solid(colors.foreground) }],
|
|
162
|
+
dataLabels: [{ color: solid(colors.foreground) }],
|
|
163
|
+
categoryLabels: [{ color: solid(colors.foregroundSecondary) }]
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
page: {
|
|
167
|
+
"*": {
|
|
168
|
+
background: [{ color: solid(colors.surface), transparency: backdrop }],
|
|
169
|
+
outspace: [{ color: solid(colors.surface), transparency: backdrop }],
|
|
170
|
+
outspacePane: [
|
|
171
|
+
{
|
|
172
|
+
backgroundColor: solid(colors.surface),
|
|
173
|
+
foregroundColor: solid(colors.foreground),
|
|
174
|
+
borderColor: solid(colors.stroke)
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
filterCard: [
|
|
178
|
+
{
|
|
179
|
+
$id: "Applied",
|
|
180
|
+
backgroundColor: solid(colors.surfaceSubtle),
|
|
181
|
+
foregroundColor: solid(colors.foreground)
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
$id: "Available",
|
|
185
|
+
backgroundColor: solid(colors.surface),
|
|
186
|
+
foregroundColor: solid(colors.foreground)
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function toHexColor(value) {
|
|
195
|
+
const text = value.trim().toLowerCase();
|
|
196
|
+
const hex = /^#([0-9a-f]{3,8})$/.exec(text)?.[1];
|
|
197
|
+
if (hex) {
|
|
198
|
+
if (hex.length === 3 || hex.length === 4) return `#${[...hex.slice(0, 3)].map((digit) => digit + digit).join("")}`;
|
|
199
|
+
if (hex.length === 6 || hex.length === 8) return `#${hex.slice(0, 6)}`;
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const rgb = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/.exec(text);
|
|
203
|
+
if (rgb) {
|
|
204
|
+
const channels2 = rgb.slice(1, 4).map((channel) => Math.max(0, Math.min(255, Math.round(Number(channel)))));
|
|
205
|
+
return `#${channels2.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function resolveColor(value, scope) {
|
|
210
|
+
const direct = toHexColor(value);
|
|
211
|
+
if (direct || !value.trim()) return direct;
|
|
212
|
+
const probe = scope.ownerDocument.createElement("span");
|
|
213
|
+
probe.style.color = value;
|
|
214
|
+
probe.style.display = "none";
|
|
215
|
+
scope.appendChild(probe);
|
|
216
|
+
try {
|
|
217
|
+
return toHexColor(getComputedStyle(probe).color);
|
|
218
|
+
} finally {
|
|
219
|
+
probe.remove();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function token(styles, scope, ...names) {
|
|
223
|
+
for (const name of names) {
|
|
224
|
+
const color = resolveColor(styles.getPropertyValue(name), scope);
|
|
225
|
+
if (color) return color;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
function surfaceBehind(element) {
|
|
230
|
+
for (let current = element.parentElement; current; current = current.parentElement) {
|
|
231
|
+
const background = getComputedStyle(current).backgroundColor;
|
|
232
|
+
const alpha = /^rgba\([^)]*,\s*([\d.]+)\s*\)$/.exec(background)?.[1];
|
|
233
|
+
if (background && background !== "transparent" && (alpha === void 0 || Number(alpha) > 0)) {
|
|
234
|
+
return toHexColor(background);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
function readPortalColors(element) {
|
|
240
|
+
const styles = getComputedStyle(element);
|
|
241
|
+
const foreground = token(styles, element, "--colorNeutralForeground1");
|
|
242
|
+
const background = token(styles, element, "--colorNeutralBackground1");
|
|
243
|
+
if (!foreground || !background) return null;
|
|
244
|
+
const surface = surfaceBehind(element) ?? background;
|
|
245
|
+
const or = (color, fallback) => color ?? fallback;
|
|
246
|
+
return {
|
|
247
|
+
surface,
|
|
248
|
+
surfaceSubtle: or(token(styles, element, "--colorNeutralBackground3", "--colorNeutralBackground2"), surface),
|
|
249
|
+
foreground,
|
|
250
|
+
foregroundSecondary: or(token(styles, element, "--colorNeutralForeground2"), foreground),
|
|
251
|
+
foregroundTertiary: or(token(styles, element, "--colorNeutralForeground3"), foreground),
|
|
252
|
+
stroke: or(token(styles, element, "--colorNeutralStroke1"), foreground),
|
|
253
|
+
strokeSubtle: or(token(styles, element, "--colorNeutralStroke3", "--colorNeutralStroke2"), surface),
|
|
254
|
+
brand: or(token(styles, element, "--colorBrandBackground"), foreground),
|
|
255
|
+
link: or(token(styles, element, "--colorBrandForegroundLink", "--colorBrandForeground1"), foreground),
|
|
256
|
+
good: or(token(styles, element, "--colorStatusSuccessForeground1", "--colorPaletteGreenForeground1"), foreground),
|
|
257
|
+
neutral: or(token(styles, element, "--colorStatusWarningForeground1", "--colorPaletteMarigoldForeground1"), foreground),
|
|
258
|
+
bad: or(token(styles, element, "--colorStatusDangerForeground1", "--colorPaletteRedForeground1"), foreground)
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function watchPortalTheme(element, onChange) {
|
|
262
|
+
let pending = false;
|
|
263
|
+
const notify = () => {
|
|
264
|
+
if (pending) return;
|
|
265
|
+
pending = true;
|
|
266
|
+
requestAnimationFrame(() => {
|
|
267
|
+
pending = false;
|
|
268
|
+
onChange();
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const observer = new MutationObserver(notify);
|
|
272
|
+
for (let ancestor = element.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
|
273
|
+
observer.observe(ancestor, { attributes: true });
|
|
274
|
+
}
|
|
275
|
+
const scheme = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
|
|
276
|
+
scheme?.addEventListener("change", notify);
|
|
277
|
+
return () => {
|
|
278
|
+
observer.disconnect();
|
|
279
|
+
scheme?.removeEventListener("change", notify);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/core/embed-report.ts
|
|
284
|
+
var TokenTypeAad = 0;
|
|
285
|
+
var TokenTypeEmbed = 1;
|
|
286
|
+
var PermissionsRead = 0;
|
|
287
|
+
var ViewModeView = 0;
|
|
288
|
+
var FilterTypeBasic = 1;
|
|
289
|
+
var BackgroundDefault = 0;
|
|
290
|
+
var BackgroundTransparent = 1;
|
|
291
|
+
var FiltersReplaceAll = 1;
|
|
292
|
+
var POWER_BI_HOST = "https://app.powerbi.com";
|
|
293
|
+
var preparedHosts = /* @__PURE__ */ new WeakMap();
|
|
294
|
+
function originOf(url) {
|
|
295
|
+
try {
|
|
296
|
+
return new URL(url).origin;
|
|
297
|
+
} catch {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
var RENEW_BEFORE_MS = 5 * 60 * 1e3;
|
|
302
|
+
var RENEW_RETRY_MS = 60 * 1e3;
|
|
303
|
+
var sharedService;
|
|
304
|
+
function powerBIService() {
|
|
305
|
+
return sharedService ??= import('powerbi-client').then(
|
|
306
|
+
({ factories, service }) => new service.Service(factories.hpmFactory, factories.wpmpFactory, factories.routerFactory)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
function toBasicFilters(filters) {
|
|
310
|
+
return (filters ?? []).map((filter) => ({
|
|
311
|
+
$schema: "http://powerbi.com/product/schema#basic",
|
|
312
|
+
target: { table: filter.table, column: filter.column },
|
|
313
|
+
operator: "In",
|
|
314
|
+
values: [...filter.values],
|
|
315
|
+
filterType: FilterTypeBasic,
|
|
316
|
+
requireSingleSelection: false
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
function toSettings(settings) {
|
|
320
|
+
return {
|
|
321
|
+
panes: {
|
|
322
|
+
filters: { visible: settings.filterPaneVisible ?? false },
|
|
323
|
+
pageNavigation: { visible: settings.pageNavigationVisible ?? true }
|
|
324
|
+
},
|
|
325
|
+
background: settings.transparentBackground ? BackgroundTransparent : BackgroundDefault,
|
|
326
|
+
...settings.language || settings.formatLocale ? {
|
|
327
|
+
localeSettings: {
|
|
328
|
+
...settings.language ? { language: settings.language } : {},
|
|
329
|
+
...settings.formatLocale ? { formatLocale: settings.formatLocale } : {}
|
|
330
|
+
}
|
|
331
|
+
} : {}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function buildEmbedConfiguration(token2, settings, theme) {
|
|
335
|
+
const common = {
|
|
336
|
+
id: token2.itemId,
|
|
337
|
+
embedUrl: token2.embedUrl,
|
|
338
|
+
accessToken: token2.embedToken,
|
|
339
|
+
tokenType: token2.tokenType === "aad" ? TokenTypeAad : TokenTypeEmbed
|
|
340
|
+
};
|
|
341
|
+
if (token2.itemType === "dashboard") return { type: "dashboard", ...common, pageView: "fitToWidth" };
|
|
342
|
+
if (token2.itemType === "tile") return { type: "tile", ...common, dashboardId: token2.dashboardId ?? "" };
|
|
343
|
+
return {
|
|
344
|
+
type: "report",
|
|
345
|
+
...common,
|
|
346
|
+
permissions: PermissionsRead,
|
|
347
|
+
viewMode: ViewModeView,
|
|
348
|
+
...settings.pageName ? { pageName: settings.pageName } : {},
|
|
349
|
+
filters: toBasicFilters(settings.filters),
|
|
350
|
+
settings: toSettings(settings),
|
|
351
|
+
...theme ? { theme: { themeJson: theme } } : {}
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function themeFor(settings, element, events = {}) {
|
|
355
|
+
if (settings.themeJson) {
|
|
356
|
+
if (typeof settings.themeJson !== "string") return settings.themeJson;
|
|
357
|
+
try {
|
|
358
|
+
return JSON.parse(settings.themeJson);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
events.error?.(`The report's theme isn't valid JSON: ${messageOf(error)}`);
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!wantsPortalTheme(settings)) return null;
|
|
365
|
+
const colors = readPortalColors(element);
|
|
366
|
+
return colors ? portalTheme(colors, { transparent: settings.transparentBackground === true }) : null;
|
|
367
|
+
}
|
|
368
|
+
function wantsPortalTheme(settings) {
|
|
369
|
+
return !settings.themeJson && settings.usePortalTheme !== false;
|
|
370
|
+
}
|
|
371
|
+
async function preparePowerBIFrame(element, signal) {
|
|
372
|
+
const powerBI = await powerBIService();
|
|
373
|
+
if (signal?.aborted) return;
|
|
374
|
+
try {
|
|
375
|
+
powerBI.bootstrap(element, { type: "report", hostname: POWER_BI_HOST });
|
|
376
|
+
preparedHosts.set(element, POWER_BI_HOST);
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function clearPowerBIFrame(element) {
|
|
381
|
+
const powerBI = await powerBIService();
|
|
382
|
+
preparedHosts.delete(element);
|
|
383
|
+
powerBI.reset(element);
|
|
384
|
+
}
|
|
385
|
+
function renewDelay(expiresOn, now) {
|
|
386
|
+
return Math.max(0, Date.parse(expiresOn) - now - RENEW_BEFORE_MS);
|
|
387
|
+
}
|
|
388
|
+
function toDataSelection(detail) {
|
|
389
|
+
const event = detail ?? {};
|
|
390
|
+
const values = [];
|
|
391
|
+
for (const point of event.dataPoints ?? []) {
|
|
392
|
+
for (const identity of point.identity ?? []) {
|
|
393
|
+
const table = identity.target?.table;
|
|
394
|
+
const column = identity.target?.column;
|
|
395
|
+
if (!table || !column) continue;
|
|
396
|
+
const equals = identity.equals;
|
|
397
|
+
values.push({
|
|
398
|
+
table,
|
|
399
|
+
column,
|
|
400
|
+
value: typeof equals === "string" || typeof equals === "number" || typeof equals === "boolean" ? equals : null
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { visualTitle: event.visual?.title || null, values };
|
|
405
|
+
}
|
|
406
|
+
function sameFilters(a, b) {
|
|
407
|
+
return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
|
|
408
|
+
}
|
|
409
|
+
function sameSettings(a, b) {
|
|
410
|
+
return JSON.stringify(toSettings(a)) === JSON.stringify(toSettings(b));
|
|
411
|
+
}
|
|
412
|
+
function messageOf(error) {
|
|
413
|
+
if (error instanceof Error) return error.message;
|
|
414
|
+
if (typeof error === "string") return error;
|
|
415
|
+
const detail = error;
|
|
416
|
+
return detail?.detailedMessage || detail?.message || "The report reported an error.";
|
|
417
|
+
}
|
|
418
|
+
async function embedPowerBIReport(element, token2, settings, events, renewToken) {
|
|
419
|
+
const powerBI = await powerBIService();
|
|
420
|
+
let shown = { ...settings };
|
|
421
|
+
let wanted = shown;
|
|
422
|
+
let loaded = false;
|
|
423
|
+
let expiresOn = token2.expiresOn;
|
|
424
|
+
let timer;
|
|
425
|
+
let renewing;
|
|
426
|
+
let disposed = false;
|
|
427
|
+
let stopWatchingTheme;
|
|
428
|
+
let reportedThemeError;
|
|
429
|
+
let pendingThemeError;
|
|
430
|
+
const themeEvents = {
|
|
431
|
+
error: (message) => {
|
|
432
|
+
if (message === reportedThemeError) return;
|
|
433
|
+
reportedThemeError = message;
|
|
434
|
+
if (loaded) events.error?.(message);
|
|
435
|
+
else pendingThemeError = message;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const isReport = token2.itemType !== "dashboard" && token2.itemType !== "tile";
|
|
439
|
+
const isTile = token2.itemType === "tile";
|
|
440
|
+
const preparedHost = preparedHosts.get(element);
|
|
441
|
+
preparedHosts.delete(element);
|
|
442
|
+
if (preparedHost && (originOf(token2.embedUrl) !== preparedHost || !isReport)) powerBI.reset(element);
|
|
443
|
+
const theme = isReport ? themeFor(settings, element, themeEvents) : null;
|
|
444
|
+
const embedded = powerBI.embed(element, buildEmbedConfiguration(token2, settings, theme));
|
|
445
|
+
const report = embedded;
|
|
446
|
+
let shownTheme = JSON.stringify(theme);
|
|
447
|
+
async function apply() {
|
|
448
|
+
const previous = shown;
|
|
449
|
+
const next = wanted;
|
|
450
|
+
shown = next;
|
|
451
|
+
if (!isReport) return;
|
|
452
|
+
if (next.pageName && next.pageName !== previous.pageName) await report.setPage(next.pageName);
|
|
453
|
+
if (!sameFilters(previous.filters, next.filters)) {
|
|
454
|
+
await report.updateFilters(FiltersReplaceAll, toBasicFilters(next.filters));
|
|
455
|
+
}
|
|
456
|
+
if (!sameSettings(previous, next)) await report.updateSettings(toSettings(next));
|
|
457
|
+
const nextTheme = themeFor(next, element, themeEvents);
|
|
458
|
+
if (nextTheme === null && wantsPortalTheme(next)) return;
|
|
459
|
+
const nextThemeKey = JSON.stringify(nextTheme);
|
|
460
|
+
if (nextThemeKey !== shownTheme) {
|
|
461
|
+
shownTheme = nextThemeKey;
|
|
462
|
+
await (nextTheme ? report.applyTheme({ themeJson: nextTheme }) : report.resetTheme());
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function applyNow() {
|
|
466
|
+
if (loaded && !disposed) void apply().catch((error) => events.error?.(messageOf(error)));
|
|
467
|
+
}
|
|
468
|
+
function watchTheme(next) {
|
|
469
|
+
if (isReport && wantsPortalTheme(next)) {
|
|
470
|
+
stopWatchingTheme ??= watchPortalTheme(element, applyNow);
|
|
471
|
+
} else {
|
|
472
|
+
stopWatchingTheme?.();
|
|
473
|
+
stopWatchingTheme = void 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function schedule(delay) {
|
|
477
|
+
clearTimeout(timer);
|
|
478
|
+
timer = setTimeout(() => void renew(), delay);
|
|
479
|
+
}
|
|
480
|
+
function renew() {
|
|
481
|
+
return renewing ??= (async () => {
|
|
482
|
+
try {
|
|
483
|
+
const next = await renewToken();
|
|
484
|
+
if (disposed) return;
|
|
485
|
+
await embedded.setAccessToken(next.embedToken);
|
|
486
|
+
expiresOn = next.expiresOn;
|
|
487
|
+
schedule(renewDelay(expiresOn, Date.now()));
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (disposed) return;
|
|
490
|
+
events.error?.(messageOf(error));
|
|
491
|
+
schedule(RENEW_RETRY_MS);
|
|
492
|
+
} finally {
|
|
493
|
+
renewing = void 0;
|
|
494
|
+
}
|
|
495
|
+
})();
|
|
496
|
+
}
|
|
497
|
+
function onVisibilityChange() {
|
|
498
|
+
if (document.visibilityState === "visible" && renewDelay(expiresOn, Date.now()) === 0) void renew();
|
|
499
|
+
}
|
|
500
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
501
|
+
schedule(renewDelay(expiresOn, Date.now()));
|
|
502
|
+
watchTheme(settings);
|
|
503
|
+
function onLoaded() {
|
|
504
|
+
if (loaded) return;
|
|
505
|
+
loaded = true;
|
|
506
|
+
events.loaded?.();
|
|
507
|
+
if (pendingThemeError) {
|
|
508
|
+
events.error?.(pendingThemeError);
|
|
509
|
+
pendingThemeError = void 0;
|
|
510
|
+
}
|
|
511
|
+
applyNow();
|
|
512
|
+
}
|
|
513
|
+
embedded.on("loaded", onLoaded);
|
|
514
|
+
embedded.on("rendered", () => events.rendered?.());
|
|
515
|
+
embedded.on("error", (event) => events.error?.(messageOf(event?.detail)));
|
|
516
|
+
if (isReport) {
|
|
517
|
+
report.on("pageChanged", (event) => {
|
|
518
|
+
const page = event?.detail?.newPage;
|
|
519
|
+
if (page) events.pageChanged?.({ name: page.name, displayName: page.displayName });
|
|
520
|
+
});
|
|
521
|
+
report.on("dataSelected", (event) => events.dataSelected?.(toDataSelection(event?.detail)));
|
|
522
|
+
} else {
|
|
523
|
+
if (isTile) embedded.on("tileLoaded", onLoaded);
|
|
524
|
+
embedded.on("tileClicked", (event) => {
|
|
525
|
+
const detail = event?.detail ?? {};
|
|
526
|
+
events.tileClicked?.({ tileId: detail.tileId ?? token2.itemId, navigationUrl: detail.navigationUrl || null });
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
async update(next) {
|
|
531
|
+
wanted = { ...next };
|
|
532
|
+
if (!disposed) watchTheme(wanted);
|
|
533
|
+
if (loaded && !disposed) await apply();
|
|
534
|
+
},
|
|
535
|
+
async refreshTheme() {
|
|
536
|
+
if (loaded && !disposed) await apply();
|
|
537
|
+
},
|
|
538
|
+
dispose() {
|
|
539
|
+
disposed = true;
|
|
540
|
+
clearTimeout(timer);
|
|
541
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
542
|
+
stopWatchingTheme?.();
|
|
543
|
+
powerBI.reset(element);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
var STRINGS_PREFIX = "app.components.powerbi-report";
|
|
548
|
+
var useStyles = makeStyles({
|
|
549
|
+
root: {
|
|
550
|
+
position: "relative",
|
|
551
|
+
minWidth: 0
|
|
552
|
+
},
|
|
553
|
+
// Power BI sizes its frame to the element it is given, so the element is what takes the size.
|
|
554
|
+
frame: {
|
|
555
|
+
width: "100%",
|
|
556
|
+
height: "100%",
|
|
557
|
+
"& iframe": { border: "none" }
|
|
558
|
+
},
|
|
559
|
+
// Kept in the layout, so Power BI sizes its frame as it loads, but not shown until the report is.
|
|
560
|
+
frameLoading: {
|
|
561
|
+
visibility: "hidden"
|
|
562
|
+
},
|
|
563
|
+
// The grid's loading overlay: a spinner over a tinted pane, the tint behind so the spinner stays
|
|
564
|
+
// at full strength.
|
|
565
|
+
overlay: {
|
|
566
|
+
position: "absolute",
|
|
567
|
+
inset: 0,
|
|
568
|
+
display: "flex",
|
|
569
|
+
alignItems: "center",
|
|
570
|
+
justifyContent: "center",
|
|
571
|
+
zIndex: 1,
|
|
572
|
+
"::before": {
|
|
573
|
+
content: '""',
|
|
574
|
+
position: "absolute",
|
|
575
|
+
inset: 0,
|
|
576
|
+
backgroundColor: tokens.colorNeutralBackground2,
|
|
577
|
+
opacity: 0.7,
|
|
578
|
+
zIndex: -1
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
function PowerBIReport(props) {
|
|
583
|
+
const {
|
|
584
|
+
reportKey,
|
|
585
|
+
pageName,
|
|
586
|
+
filters,
|
|
587
|
+
filterPaneVisible,
|
|
588
|
+
pageNavigationVisible,
|
|
589
|
+
transparentBackground,
|
|
590
|
+
usePortalTheme,
|
|
591
|
+
themeJson,
|
|
592
|
+
height = "600px",
|
|
593
|
+
width = "100%",
|
|
594
|
+
className,
|
|
595
|
+
style
|
|
596
|
+
} = props;
|
|
597
|
+
const { ppp } = usePowerPortalsPro();
|
|
598
|
+
const { locale } = useLocale();
|
|
599
|
+
const t = usePrefixedT(STRINGS_PREFIX);
|
|
600
|
+
const styles = useStyles();
|
|
601
|
+
const frameRef = useRef(null);
|
|
602
|
+
const embeddedRef = useRef(null);
|
|
603
|
+
const [failure, setFailure] = useState(null);
|
|
604
|
+
const [loading, setLoading] = useState(true);
|
|
605
|
+
const propsRef = useRef(props);
|
|
606
|
+
propsRef.current = props;
|
|
607
|
+
const settings = {
|
|
608
|
+
pageName: pageName ?? null,
|
|
609
|
+
filters: filters ?? null,
|
|
610
|
+
filterPaneVisible,
|
|
611
|
+
pageNavigationVisible,
|
|
612
|
+
transparentBackground,
|
|
613
|
+
language: locale,
|
|
614
|
+
formatLocale: locale,
|
|
615
|
+
usePortalTheme,
|
|
616
|
+
themeJson
|
|
617
|
+
};
|
|
618
|
+
const settingsRef = useRef(settings);
|
|
619
|
+
settingsRef.current = settings;
|
|
620
|
+
useEffect(() => {
|
|
621
|
+
const element = frameRef.current;
|
|
622
|
+
if (!element) return;
|
|
623
|
+
const controller = new AbortController();
|
|
624
|
+
let embedded = null;
|
|
625
|
+
setFailure(null);
|
|
626
|
+
setLoading(true);
|
|
627
|
+
const issue = () => ppp.getPowerBIEmbedTokenAsync({ reportKey }, controller.signal);
|
|
628
|
+
void preparePowerBIFrame(element, controller.signal).catch(() => void 0);
|
|
629
|
+
issue().then(async (token2) => {
|
|
630
|
+
if (controller.signal.aborted) return;
|
|
631
|
+
const report = await embedPowerBIReport(
|
|
632
|
+
element,
|
|
633
|
+
token2,
|
|
634
|
+
settingsRef.current,
|
|
635
|
+
{
|
|
636
|
+
loaded: () => {
|
|
637
|
+
setLoading(false);
|
|
638
|
+
propsRef.current.onLoaded?.();
|
|
639
|
+
},
|
|
640
|
+
rendered: () => propsRef.current.onRendered?.(),
|
|
641
|
+
// One before the report loads is Power BI's to show, in its frame, so the frame comes out.
|
|
642
|
+
error: (message) => {
|
|
643
|
+
setLoading(false);
|
|
644
|
+
propsRef.current.onError?.(message);
|
|
645
|
+
},
|
|
646
|
+
pageChanged: (page) => propsRef.current.onPageChanged?.(page),
|
|
647
|
+
dataSelected: (selection) => propsRef.current.onDataSelected?.(selection),
|
|
648
|
+
tileClicked: (click) => propsRef.current.onTileClicked?.(click)
|
|
649
|
+
},
|
|
650
|
+
issue
|
|
651
|
+
);
|
|
652
|
+
if (controller.signal.aborted) {
|
|
653
|
+
report.dispose();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
embedded = report;
|
|
657
|
+
embeddedRef.current = report;
|
|
658
|
+
await report.update(settingsRef.current);
|
|
659
|
+
}).catch((error) => {
|
|
660
|
+
if (controller.signal.aborted) return;
|
|
661
|
+
setFailure(
|
|
662
|
+
error instanceof PowerBIConnectionRequiredError ? "connect" : error instanceof PowerPortalsProError && error.status === 403 ? "unavailable" : "failed"
|
|
663
|
+
);
|
|
664
|
+
setLoading(false);
|
|
665
|
+
propsRef.current.onError?.(error instanceof Error ? error.message : String(error));
|
|
666
|
+
void clearPowerBIFrame(element).catch(() => void 0);
|
|
667
|
+
});
|
|
668
|
+
return () => {
|
|
669
|
+
controller.abort();
|
|
670
|
+
if (embedded) embedded.dispose();
|
|
671
|
+
else void clearPowerBIFrame(element).catch(() => void 0);
|
|
672
|
+
embeddedRef.current = null;
|
|
673
|
+
};
|
|
674
|
+
}, [ppp, reportKey]);
|
|
675
|
+
const settingsKey = JSON.stringify(settings);
|
|
676
|
+
useEffect(() => {
|
|
677
|
+
void embeddedRef.current?.update(settingsRef.current).catch(
|
|
678
|
+
(error) => propsRef.current.onError?.(error instanceof Error ? error.message : String(error))
|
|
679
|
+
);
|
|
680
|
+
}, [settingsKey]);
|
|
681
|
+
return /* @__PURE__ */ jsxs("div", { className: mergeClasses("ppp-powerbi-report", styles.root, className), style: { height, width, ...style }, children: [
|
|
682
|
+
failure === "connect" ? /* @__PURE__ */ jsxs(MessageBar, { intent: "info", children: [
|
|
683
|
+
/* @__PURE__ */ jsx(MessageBarBody, { children: t("connect-description") }),
|
|
684
|
+
/* @__PURE__ */ jsx(MessageBarActions, { children: /* @__PURE__ */ jsx(
|
|
685
|
+
Button,
|
|
686
|
+
{
|
|
687
|
+
size: "small",
|
|
688
|
+
appearance: "primary",
|
|
689
|
+
onClick: () => window.location.assign(
|
|
690
|
+
ppp.powerBIConnectUrl(window.location.pathname + window.location.search + window.location.hash)
|
|
691
|
+
),
|
|
692
|
+
children: t("connect")
|
|
693
|
+
}
|
|
694
|
+
) })
|
|
695
|
+
] }) : failure ? /* @__PURE__ */ jsx(MessageBar, { intent: failure === "unavailable" ? "warning" : "error", children: /* @__PURE__ */ jsx(MessageBarBody, { children: t(failure) }) }) : null,
|
|
696
|
+
loading && failure === null ? /* @__PURE__ */ jsx("div", { className: styles.overlay, children: /* @__PURE__ */ jsx(Spinner, { size: "large", label: t("loading") }) }) : null,
|
|
697
|
+
/* @__PURE__ */ jsx(
|
|
698
|
+
"div",
|
|
699
|
+
{
|
|
700
|
+
ref: frameRef,
|
|
701
|
+
className: mergeClasses(styles.frame, loading && styles.frameLoading),
|
|
702
|
+
hidden: failure !== null
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
] });
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export { PowerBIReport };
|
|
709
|
+
//# sourceMappingURL=index.js.map
|
|
710
|
+
//# sourceMappingURL=index.js.map
|