@backstage/frontend-app-api 0.4.1-next.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @backstage/frontend-app-api
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d4149bf: **BREAKING**: Renamed the `app/router` extension to `app/root`.
8
+ - 074dfe3: Attaching extensions to an input that does not exist is now a warning rather than an error.
9
+
10
+ ### Patch Changes
11
+
12
+ - 7d63b32: Accepts sub route refs on the new `createPlugin` routes map.
13
+ - 516fd3e: Updated README to reflect release status
14
+ - c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI).
15
+ - 5fe6600: add oauth dialog and alert display to the root elements
16
+ - Updated dependencies
17
+ - @backstage/frontend-plugin-api@0.5.0
18
+ - @backstage/core-components@0.13.10
19
+ - @backstage/core-plugin-api@1.8.2
20
+ - @backstage/config@1.1.1
21
+ - @backstage/core-app-api@1.11.3
22
+ - @backstage/errors@1.2.3
23
+ - @backstage/theme@0.5.0
24
+ - @backstage/types@1.1.1
25
+ - @backstage/version-bridge@1.0.7
26
+
3
27
  ## 0.4.1-next.2
4
28
 
5
29
  ### Patch Changes
package/dist/index.esm.js CHANGED
@@ -1,8 +1,8 @@
1
- import React, { useMemo, useState, useEffect, createContext, useContext } from 'react';
1
+ import React, { useMemo, useState, useEffect, createContext, Fragment, useContext } from 'react';
2
2
  import { ConfigReader } from '@backstage/config';
3
- import { createExtension, createExtensionInput, createApiExtension, createThemeExtension, createComponentExtension, createTranslationExtension, coreExtensionData, useComponentRef, coreComponentRefs, createNavItemExtension, createNavLogoExtension, useRouteRef, AnalyticsContext, useAnalytics, createSignInPageExtension, appTreeApiRef, componentsApiRef } from '@backstage/frontend-plugin-api';
3
+ import { createExtension, createExtensionInput, createApiExtension, createThemeExtension, createComponentExtension, createTranslationExtension, coreExtensionData, useComponentRef, coreComponentRefs, createNavItemExtension, createNavLogoExtension, useRouteRef, createAppRootElementExtension, createSchemaFromZod, AnalyticsContext, useAnalytics, createRouterExtension, createSignInPageExtension, createAppRootWrapperExtension, appTreeApiRef, componentsApiRef } from '@backstage/frontend-plugin-api';
4
4
  import { useRoutes, BrowserRouter, useInRouterContext, MemoryRouter, matchRoutes, generatePath, useLocation, Route } from 'react-router-dom';
5
- import { SidebarPage, sidebarConfig, Sidebar, SidebarDivider, useSidebarOpenState, Link, SidebarItem, Progress, ErrorPage, ErrorPanel } from '@backstage/core-components';
5
+ import { SidebarPage, sidebarConfig, Sidebar, SidebarDivider, useSidebarOpenState, Link, SidebarItem, Progress, ErrorPage, ErrorPanel, OAuthRequestDialog, AlertDisplay } from '@backstage/core-components';
6
6
  import { makeStyles, Button as Button$1 } from '@material-ui/core';
7
7
  import { useApi, appThemeApiRef, FeatureFlagState, createApiFactory, discoveryApiRef, configApiRef, alertApiRef, analyticsApiRef, errorApiRef, storageApiRef, fetchApiRef, identityApiRef, oauthRequestApiRef, googleAuthApiRef, microsoftAuthApiRef, githubAuthApiRef, oktaAuthApiRef, gitlabAuthApiRef, oneloginAuthApiRef, bitbucketAuthApiRef, bitbucketServerAuthApiRef, atlassianAuthApiRef, attachComponentData, featureFlagsApiRef } from '@backstage/core-plugin-api';
8
8
  import { UrlPatternDiscovery, AlertApiForwarder, NoOpAnalyticsApi, ErrorAlerter, ErrorApiForwarder, UnhandledErrorForwarder, WebStorage, createFetchApi, FetchMiddlewares, OAuthRequestManager, GoogleAuth, MicrosoftAuth, GithubAuth, OktaAuth, GitlabAuth, OneLoginAuth, BitbucketAuth, BitbucketServerAuth, AtlassianAuth, ApiFactoryRegistry, AppThemeSelector, ApiResolver, ApiProvider } from '@backstage/core-app-api';
@@ -40,7 +40,7 @@ import { appLanguageApiRef, translationApiRef } from '@backstage/core-plugin-api
40
40
  import mapValues from 'lodash/mapValues';
41
41
  import { stringifyError } from '@backstage/errors';
42
42
 
43
- const Core = createExtension({
43
+ const App = createExtension({
44
44
  namespace: "app",
45
45
  attachTo: { id: "root", input: "default" },
46
46
  // ignored
@@ -74,7 +74,7 @@ const Core = createExtension({
74
74
  }
75
75
  });
76
76
 
77
- const CoreRoutes = createExtension({
77
+ const AppRoutes = createExtension({
78
78
  namespace: "app",
79
79
  name: "routes",
80
80
  attachTo: { id: "app/layout", input: "content" },
@@ -111,10 +111,10 @@ const CoreRoutes = createExtension({
111
111
  }
112
112
  });
113
113
 
114
- const CoreLayout = createExtension({
114
+ const AppLayout = createExtension({
115
115
  namespace: "app",
116
116
  name: "layout",
117
- attachTo: { id: "app/router", input: "children" },
117
+ attachTo: { id: "app/root", input: "children" },
118
118
  inputs: {
119
119
  nav: createExtensionInput(
120
120
  {
@@ -220,7 +220,7 @@ const SidebarNavItem = (props) => {
220
220
  const to = useRouteRef(routeRef)();
221
221
  return /* @__PURE__ */ React.createElement(SidebarItem, { to, icon: Icon, text: title });
222
222
  };
223
- const CoreNav = createExtension({
223
+ const AppNav = createExtension({
224
224
  namespace: "app",
225
225
  name: "nav",
226
226
  attachTo: { id: "app/layout", input: "nav" },
@@ -1106,11 +1106,15 @@ function resolveExtensionDefinition(definition, context) {
1106
1106
  `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`
1107
1107
  );
1108
1108
  }
1109
+ const id = kind ? `${kind}:${namePart}` : namePart;
1109
1110
  return {
1110
1111
  ...rest,
1111
1112
  $$type: "@backstage/Extension",
1112
1113
  version: "v1",
1113
- id: kind ? `${kind}:${namePart}` : namePart
1114
+ id,
1115
+ toString() {
1116
+ return `Extension{id=${id}}`;
1117
+ }
1114
1118
  };
1115
1119
  }
1116
1120
 
@@ -1367,6 +1371,26 @@ const DarkTheme = createThemeExtension({
1367
1371
  Provider: ({ children }) => /* @__PURE__ */ React.createElement(UnifiedThemeProvider, { theme: themes.dark, children })
1368
1372
  });
1369
1373
 
1374
+ const oauthRequestDialogAppRootElement = createAppRootElementExtension({
1375
+ namespace: "app",
1376
+ name: "oauth-request-dialog",
1377
+ element: /* @__PURE__ */ React.createElement(OAuthRequestDialog, null)
1378
+ });
1379
+ const alertDisplayAppRootElement = createAppRootElementExtension({
1380
+ namespace: "app",
1381
+ name: "alert-display",
1382
+ configSchema: createSchemaFromZod(
1383
+ (z) => z.object({
1384
+ transientTimeoutMs: z.number().default(5e3),
1385
+ anchorOrigin: z.object({
1386
+ vertical: z.enum(["top", "bottom"]).default("top"),
1387
+ horizontal: z.enum(["left", "center", "right"]).default("center")
1388
+ }).default({})
1389
+ })
1390
+ ),
1391
+ element: ({ config }) => /* @__PURE__ */ React.createElement(AlertDisplay, { ...config })
1392
+ });
1393
+
1370
1394
  const legacyPluginStore = getOrCreateGlobalSingleton(
1371
1395
  "legacy-plugin-compatibility-store",
1372
1396
  () => /* @__PURE__ */ new WeakMap()
@@ -1731,9 +1755,14 @@ function collectRouteIds(features) {
1731
1755
  if (routesById.has(refId)) {
1732
1756
  throw new Error(`Unexpected duplicate route '${refId}'`);
1733
1757
  }
1734
- const internalRef = toInternalRouteRef(ref);
1735
- internalRef.setId(refId);
1736
- routesById.set(refId, ref);
1758
+ if (isRouteRef(ref)) {
1759
+ const internalRef = toInternalRouteRef(ref);
1760
+ internalRef.setId(refId);
1761
+ routesById.set(refId, ref);
1762
+ } else {
1763
+ const internalRef = toInternalSubRouteRef(ref);
1764
+ routesById.set(refId, internalRef);
1765
+ }
1737
1766
  }
1738
1767
  for (const [name, ref] of Object.entries(feature.externalRoutes)) {
1739
1768
  const refId = `plugin.${feature.id}.externalRoutes.${name}`;
@@ -2144,16 +2173,22 @@ function resolveInputData(dataMap, attachment, inputName) {
2144
2173
  return value;
2145
2174
  });
2146
2175
  }
2147
- function resolveInputs(inputMap, attachments) {
2176
+ function resolveInputs(id, inputMap, attachments) {
2148
2177
  const undeclaredAttachments = Array.from(attachments.entries()).filter(
2149
2178
  ([inputName]) => inputMap[inputName] === void 0
2150
2179
  );
2151
- if (undeclaredAttachments.length > 0) {
2152
- throw new Error(
2153
- `received undeclared input${undeclaredAttachments.length > 1 ? "s" : ""} ${undeclaredAttachments.map(
2154
- ([k, exts]) => `'${k}' from extension${exts.length > 1 ? "s" : ""} '${exts.map((e) => e.spec.id).join("', '")}'`
2155
- ).join(" and ")}`
2156
- );
2180
+ if (process.env.NODE_ENV !== "production") {
2181
+ const inputNames = Object.keys(inputMap);
2182
+ for (const [name, nodes] of undeclaredAttachments) {
2183
+ const pl = nodes.length > 1;
2184
+ console.warn(
2185
+ [
2186
+ `The extension${pl ? "s" : ""} '${nodes.map((n) => n.spec.id).join("', '")}' ${pl ? "are" : "is"}`,
2187
+ `attached to the input '${name}' of the extension '${id}', but it`,
2188
+ inputNames.length === 0 ? "has no inputs" : `has no such input (candidates are '${inputNames.join("', '")}')`
2189
+ ].join(" ")
2190
+ );
2191
+ }
2157
2192
  }
2158
2193
  return mapValues(inputMap, (input, inputName) => {
2159
2194
  var _a;
@@ -2206,7 +2241,7 @@ function createAppNodeInstance(options) {
2206
2241
  const namedOutputs = internalExtension.factory({
2207
2242
  node,
2208
2243
  config: parsedConfig,
2209
- inputs: resolveInputs(internalExtension.inputs, attachments)
2244
+ inputs: resolveInputs(id, internalExtension.inputs, attachments)
2210
2245
  });
2211
2246
  for (const [name, output] of Object.entries(namedOutputs)) {
2212
2247
  const ref = internalExtension.output[name];
@@ -2367,31 +2402,48 @@ const RouteTracker = ({
2367
2402
  ));
2368
2403
  };
2369
2404
 
2370
- const CoreRouter = createExtension({
2405
+ const AppRoot = createExtension({
2371
2406
  namespace: "app",
2372
- name: "router",
2407
+ name: "root",
2373
2408
  attachTo: { id: "app", input: "root" },
2374
2409
  inputs: {
2410
+ router: createExtensionInput(
2411
+ { component: createRouterExtension.componentDataRef },
2412
+ { singleton: true, optional: true }
2413
+ ),
2375
2414
  signInPage: createExtensionInput(
2376
- {
2377
- component: createSignInPageExtension.componentDataRef
2378
- },
2415
+ { component: createSignInPageExtension.componentDataRef },
2379
2416
  { singleton: true, optional: true }
2380
2417
  ),
2381
2418
  children: createExtensionInput(
2382
- {
2383
- element: coreExtensionData.reactElement
2384
- },
2419
+ { element: coreExtensionData.reactElement },
2385
2420
  { singleton: true }
2386
- )
2421
+ ),
2422
+ elements: createExtensionInput({
2423
+ element: coreExtensionData.reactElement
2424
+ }),
2425
+ wrappers: createExtensionInput({
2426
+ component: createAppRootWrapperExtension.componentDataRef
2427
+ })
2387
2428
  },
2388
2429
  output: {
2389
2430
  element: coreExtensionData.reactElement
2390
2431
  },
2391
2432
  factory({ inputs }) {
2392
- var _a;
2433
+ var _a, _b;
2434
+ let content = /* @__PURE__ */ React.createElement(React.Fragment, null, inputs.elements.map((el) => /* @__PURE__ */ React.createElement(Fragment, { key: el.node.spec.id }, el.output.element)), inputs.children.output.element);
2435
+ for (const wrapper of inputs.wrappers) {
2436
+ content = /* @__PURE__ */ React.createElement(wrapper.output.component, null, content);
2437
+ }
2393
2438
  return {
2394
- element: /* @__PURE__ */ React.createElement(AppRouter, { SignInPageComponent: (_a = inputs.signInPage) == null ? void 0 : _a.output.component }, inputs.children.output.element)
2439
+ element: /* @__PURE__ */ React.createElement(
2440
+ AppRouter,
2441
+ {
2442
+ SignInPageComponent: (_a = inputs.signInPage) == null ? void 0 : _a.output.component,
2443
+ RouterComponent: (_b = inputs.router) == null ? void 0 : _b.output.component
2444
+ },
2445
+ content
2446
+ )
2395
2447
  };
2396
2448
  }
2397
2449
  });
@@ -2421,8 +2473,17 @@ function SignInPageWrapper({
2421
2473
  });
2422
2474
  return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
2423
2475
  }
2476
+ function DefaultRouter(props) {
2477
+ const configApi = useApi(configApiRef);
2478
+ const basePath = getBasePath(configApi);
2479
+ return /* @__PURE__ */ React.createElement(BrowserRouter, { basename: basePath }, props.children);
2480
+ }
2424
2481
  function AppRouter(props) {
2425
- const { children, SignInPageComponent } = props;
2482
+ const {
2483
+ children,
2484
+ SignInPageComponent,
2485
+ RouterComponent = DefaultRouter
2486
+ } = props;
2426
2487
  const configApi = useApi(configApiRef);
2427
2488
  const basePath = getBasePath(configApi);
2428
2489
  const internalAppContext = useContext(InternalAppContext);
@@ -2454,9 +2515,9 @@ function AppRouter(props) {
2454
2515
  },
2455
2516
  { signOutTargetUrl: basePath || "/" }
2456
2517
  );
2457
- return /* @__PURE__ */ React.createElement(BrowserRouter, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), children);
2518
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), children);
2458
2519
  }
2459
- return /* @__PURE__ */ React.createElement(BrowserRouter, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
2520
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
2460
2521
  SignInPageWrapper,
2461
2522
  {
2462
2523
  component: SignInPageComponent,
@@ -2502,16 +2563,18 @@ _components = new WeakMap();
2502
2563
 
2503
2564
  const DefaultApis = apis.map((factory) => createApiExtension({ factory }));
2504
2565
  const builtinExtensions = [
2505
- Core,
2506
- CoreRouter,
2507
- CoreRoutes,
2508
- CoreNav,
2509
- CoreLayout,
2566
+ App,
2567
+ AppRoot,
2568
+ AppRoutes,
2569
+ AppNav,
2570
+ AppLayout,
2510
2571
  DefaultProgressComponent,
2511
2572
  DefaultErrorBoundaryComponent,
2512
2573
  DefaultNotFoundErrorPageComponent,
2513
2574
  LightTheme,
2514
2575
  DarkTheme,
2576
+ oauthRequestDialogAppRootElement,
2577
+ alertDisplayAppRootElement,
2515
2578
  ...DefaultApis
2516
2579
  ].map((def) => resolveExtensionDefinition(def));
2517
2580
  function createExtensionTree(options) {
@@ -2666,7 +2729,7 @@ function createSpecializedApp(options) {
2666
2729
  collectRouteIds(features)
2667
2730
  );
2668
2731
  const rootEl = tree.root.instance.getData(coreExtensionData.reactElement);
2669
- const App = () => /* @__PURE__ */ React.createElement(ApiProvider, { apis: apiHolder }, /* @__PURE__ */ React.createElement(AppThemeProvider, null, /* @__PURE__ */ React.createElement(RoutingProvider, { ...routeInfo, routeBindings }, /* @__PURE__ */ React.createElement(
2732
+ const AppComponent = () => /* @__PURE__ */ React.createElement(ApiProvider, { apis: apiHolder }, /* @__PURE__ */ React.createElement(AppThemeProvider, null, /* @__PURE__ */ React.createElement(RoutingProvider, { ...routeInfo, routeBindings }, /* @__PURE__ */ React.createElement(
2670
2733
  InternalAppContext.Provider,
2671
2734
  {
2672
2735
  value: { appIdentityProxy, routeObjects: routeInfo.routeObjects }
@@ -2675,7 +2738,7 @@ function createSpecializedApp(options) {
2675
2738
  ))));
2676
2739
  return {
2677
2740
  createRoot() {
2678
- return /* @__PURE__ */ React.createElement(App, null);
2741
+ return /* @__PURE__ */ React.createElement(AppComponent, null);
2679
2742
  }
2680
2743
  };
2681
2744
  }