@nospt/backstage-plugin-apigee 1.1.0-rc4 → 1.1.0-rc5

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.

Potentially problematic release.


This version of @nospt/backstage-plugin-apigee might be problematic. Click here for more details.

Files changed (41) hide show
  1. package/config.d.ts +0 -10
  2. package/dist/components/ApiCard.esm.js +24 -15
  3. package/dist/components/ApiCard.esm.js.map +1 -1
  4. package/dist/components/ApiCard.module.css.esm.js +2 -2
  5. package/dist/components/ApiCardGrid.esm.js +3 -11
  6. package/dist/components/ApiCardGrid.esm.js.map +1 -1
  7. package/dist/components/ApiDetailsDrawer.esm.js +335 -0
  8. package/dist/components/ApiDetailsDrawer.esm.js.map +1 -0
  9. package/dist/components/ApiDetailsDrawer.module.css.esm.js +8 -0
  10. package/dist/components/ApiDetailsDrawer.module.css.esm.js.map +1 -0
  11. package/dist/components/ApiTable.esm.js +10 -2
  12. package/dist/components/ApiTable.esm.js.map +1 -1
  13. package/dist/components/ApiTable.module.css.esm.js +2 -2
  14. package/dist/components/ApigeeApiCatalogPage.esm.js +62 -49
  15. package/dist/components/ApigeeApiCatalogPage.esm.js.map +1 -1
  16. package/dist/components/FilterToolbar.esm.js +27 -26
  17. package/dist/components/FilterToolbar.esm.js.map +1 -1
  18. package/dist/components/FilterToolbar.module.css.esm.js +2 -2
  19. package/dist/components/apiCardViewModel.esm.js +1 -2
  20. package/dist/components/apiCardViewModel.esm.js.map +1 -1
  21. package/dist/components/apiDetailViewModel.esm.js +60 -0
  22. package/dist/components/apiDetailViewModel.esm.js.map +1 -0
  23. package/dist/components/apigeeFacetFilters.esm.js +3 -6
  24. package/dist/components/apigeeFacetFilters.esm.js.map +1 -1
  25. package/dist/components/apigeeSearchParams.esm.js +0 -1
  26. package/dist/components/apigeeSearchParams.esm.js.map +1 -1
  27. package/dist/components/facetModel.esm.js +3 -29
  28. package/dist/components/facetModel.esm.js.map +1 -1
  29. package/dist/components/useApigeeFilters.esm.js +1 -3
  30. package/dist/components/useApigeeFilters.esm.js.map +1 -1
  31. package/dist/constants.esm.js +1 -2
  32. package/dist/constants.esm.js.map +1 -1
  33. package/dist/index.d.ts +52 -1
  34. package/dist/index.esm.js +1 -1
  35. package/dist/plugin.esm.js +17 -7
  36. package/dist/plugin.esm.js.map +1 -1
  37. package/dist/routes.esm.js +2 -1
  38. package/dist/routes.esm.js.map +1 -1
  39. package/package.json +2 -2
  40. package/dist/components/categoryIcons.esm.js +0 -53
  41. package/dist/components/categoryIcons.esm.js.map +0 -1
@@ -54,7 +54,7 @@ function SortableHeader({
54
54
  }
55
55
  ) });
56
56
  }
57
- function ApiTable({ vms }) {
57
+ function ApiTable({ vms, onSelect }) {
58
58
  const entityRoute = useRouteRef(entityRouteRef);
59
59
  const [sort, setSort] = useState(null);
60
60
  const handleSort = (key) => setSort(
@@ -131,7 +131,15 @@ function ApiTable({ vms }) {
131
131
  const { kind, namespace, name } = vm.targetRef;
132
132
  const href = entityRoute ? entityRoute({ kind: kind.toLowerCase(), namespace, name }) : void 0;
133
133
  return /* @__PURE__ */ jsxs("tr", { children: [
134
- /* @__PURE__ */ jsx("td", { className: styles.nameCell, children: href ? /* @__PURE__ */ jsx(Link, { to: href, className: styles.nameLink, children: vm.name }) : vm.name }),
134
+ /* @__PURE__ */ jsx("td", { className: styles.nameCell, children: onSelect ? /* @__PURE__ */ jsx(
135
+ "button",
136
+ {
137
+ type: "button",
138
+ className: styles.nameButton,
139
+ onClick: () => onSelect(vm),
140
+ children: vm.name
141
+ }
142
+ ) : href ? /* @__PURE__ */ jsx(Link, { to: href, className: styles.nameLink, children: vm.name }) : vm.name }),
135
143
  /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(LifecycleBadge, { kind: vm.badge.kind, label: vm.badge.label }) }),
136
144
  /* @__PURE__ */ jsx("td", { children: vm.version ?? "\u2014" }),
137
145
  /* @__PURE__ */ jsx("td", { children: vm.ownerLabel }),
@@ -1 +1 @@
1
- {"version":3,"file":"ApiTable.esm.js","sources":["../../src/components/ApiTable.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { Link } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\nimport { entityRouteRef } from '@backstage/plugin-catalog-react';\nimport {\n RiArrowDownSLine,\n RiArrowUpDownLine,\n RiArrowUpSLine,\n RiFileTextLine,\n} from '@remixicon/react';\nimport { useMemo, useState } from 'react';\nimport { LifecycleBadge, VISIBILITY_ICON } from './ApiCard';\nimport type { ApiCardViewModel } from './apiCardViewModel';\nimport styles from './ApiTable.module.css';\n\nexport interface ApiTableProps {\n vms: ApiCardViewModel[];\n}\n\ntype SortKey = 'name' | 'lifecycle' | 'version' | 'owner' | 'department' | 'orgs';\ntype SortState = { key: SortKey; dir: 'asc' | 'desc' };\n\n/** Alphabetical sort key per column; missing values collapse to '' (sort first asc). */\nfunction sortValue(vm: ApiCardViewModel, key: SortKey): string {\n switch (key) {\n case 'name':\n return vm.name;\n case 'lifecycle':\n return vm.badge.label;\n case 'version':\n return vm.version ?? '';\n case 'owner':\n return vm.ownerLabel;\n case 'department':\n return vm.department ?? '';\n case 'orgs':\n return vm.visibilities.join(', ');\n default:\n return '';\n }\n}\n\n/** Clickable column header that toggles asc/desc alphabetical sort for its column. */\nfunction SortableHeader({\n label,\n sortKey,\n sort,\n onSort,\n}: {\n label: string;\n sortKey: SortKey;\n sort: SortState | null;\n onSort: (key: SortKey) => void;\n}) {\n const activeSort = sort && sort.key === sortKey ? sort : null;\n const ariaSort = activeSort\n ? activeSort.dir === 'asc'\n ? 'ascending'\n : 'descending'\n : 'none';\n const Icon = activeSort\n ? activeSort.dir === 'asc'\n ? RiArrowUpSLine\n : RiArrowDownSLine\n : RiArrowUpDownLine;\n return (\n <th scope=\"col\" aria-sort={ariaSort}>\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => onSort(sortKey)}\n >\n {label}\n <Icon\n aria-hidden\n size={14}\n className={activeSort ? styles.sortIcon : styles.sortIconIdle}\n />\n </button>\n </th>\n );\n}\n\n/**\n * Tabular (\"list\") presentation of the same view-models rendered as tiles by `ApiCard`.\n * Each row's name is the entity link (resolved via `entityRouteRef`, degrading to plain\n * text when the route ref is unbound); one anchor per row keeps the table keyboard\n * accessible. Reuses `ApiCard`'s lifecycle badge and org icons. (UI-01)\n */\nexport function ApiTable({ vms }: ApiTableProps) {\n const entityRoute = useRouteRef(entityRouteRef);\n const [sort, setSort] = useState<SortState | null>(null);\n\n const handleSort = (key: SortKey) =>\n setSort(prev =>\n prev?.key === key\n ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' }\n : { key, dir: 'asc' },\n );\n\n const sortedVms = useMemo(() => {\n if (!sort) {\n return vms;\n }\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...vms].sort(\n (a, b) => dir * sortValue(a, sort.key).localeCompare(sortValue(b, sort.key)),\n );\n }, [vms, sort]);\n\n return (\n <div className={styles.tableWrap}>\n <table className={styles.table}>\n <thead>\n <tr>\n <SortableHeader\n label=\"Name\"\n sortKey=\"name\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Lifecycle\"\n sortKey=\"lifecycle\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Version\"\n sortKey=\"version\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Owner\"\n sortKey=\"owner\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Department\"\n sortKey=\"department\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Orgs\"\n sortKey=\"orgs\"\n sort={sort}\n onSort={handleSort}\n />\n <th scope=\"col\">Spec</th>\n </tr>\n </thead>\n <tbody>\n {sortedVms.map(vm => {\n const { kind, namespace, name } = vm.targetRef;\n const href = entityRoute\n ? entityRoute({ kind: kind.toLowerCase(), namespace, name })\n : undefined;\n return (\n <tr key={vm.targetRef.name}>\n <td className={styles.nameCell}>\n {href ? (\n <Link to={href} className={styles.nameLink}>\n {vm.name}\n </Link>\n ) : (\n vm.name\n )}\n </td>\n <td>\n <LifecycleBadge kind={vm.badge.kind} label={vm.badge.label} />\n </td>\n <td>{vm.version ?? '—'}</td>\n <td>{vm.ownerLabel}</td>\n <td>{vm.department ?? '—'}</td>\n <td>\n {vm.visibilities.length > 0 ? (\n <span className={styles.orgs}>\n {vm.visibilities.map(visibility => {\n const VisibilityIcon = VISIBILITY_ICON[visibility];\n return (\n <span key={visibility} className={styles.org}>\n <VisibilityIcon aria-hidden size={14} />\n {visibility}\n </span>\n );\n })}\n </span>\n ) : (\n '—'\n )}\n </td>\n <td>\n {vm.hasSpec ? (\n <span\n role=\"img\"\n aria-label=\"OpenAPI spec available\"\n title=\"OpenAPI spec available\"\n className={styles.specIcon}\n >\n <RiFileTextLine aria-hidden size={16} />\n </span>\n ) : (\n '—'\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ApiTable.esm.js","sources":["../../src/components/ApiTable.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { Link } from '@backstage/core-components';\nimport { useRouteRef } from '@backstage/frontend-plugin-api';\nimport { entityRouteRef } from '@backstage/plugin-catalog-react';\nimport {\n RiArrowDownSLine,\n RiArrowUpDownLine,\n RiArrowUpSLine,\n RiFileTextLine,\n} from '@remixicon/react';\nimport { useMemo, useState } from 'react';\nimport { LifecycleBadge, VISIBILITY_ICON } from './ApiCard';\nimport type { ApiCardViewModel } from './apiCardViewModel';\nimport styles from './ApiTable.module.css';\n\nexport interface ApiTableProps {\n vms: ApiCardViewModel[];\n /** When provided, clicking a row name opens the details drawer instead of\n * navigating to the catalog entity page. */\n onSelect?: (vm: ApiCardViewModel) => void;\n}\n\ntype SortKey = 'name' | 'lifecycle' | 'version' | 'owner' | 'department' | 'orgs';\ntype SortState = { key: SortKey; dir: 'asc' | 'desc' };\n\n/** Alphabetical sort key per column; missing values collapse to '' (sort first asc). */\nfunction sortValue(vm: ApiCardViewModel, key: SortKey): string {\n switch (key) {\n case 'name':\n return vm.name;\n case 'lifecycle':\n return vm.badge.label;\n case 'version':\n return vm.version ?? '';\n case 'owner':\n return vm.ownerLabel;\n case 'department':\n return vm.department ?? '';\n case 'orgs':\n return vm.visibilities.join(', ');\n default:\n return '';\n }\n}\n\n/** Clickable column header that toggles asc/desc alphabetical sort for its column. */\nfunction SortableHeader({\n label,\n sortKey,\n sort,\n onSort,\n}: {\n label: string;\n sortKey: SortKey;\n sort: SortState | null;\n onSort: (key: SortKey) => void;\n}) {\n const activeSort = sort && sort.key === sortKey ? sort : null;\n const ariaSort = activeSort\n ? activeSort.dir === 'asc'\n ? 'ascending'\n : 'descending'\n : 'none';\n const Icon = activeSort\n ? activeSort.dir === 'asc'\n ? RiArrowUpSLine\n : RiArrowDownSLine\n : RiArrowUpDownLine;\n return (\n <th scope=\"col\" aria-sort={ariaSort}>\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => onSort(sortKey)}\n >\n {label}\n <Icon\n aria-hidden\n size={14}\n className={activeSort ? styles.sortIcon : styles.sortIconIdle}\n />\n </button>\n </th>\n );\n}\n\n/**\n * Tabular (\"list\") presentation of the same view-models rendered as tiles by `ApiCard`.\n * Each row's name is the entity link (resolved via `entityRouteRef`, degrading to plain\n * text when the route ref is unbound); one anchor per row keeps the table keyboard\n * accessible. Reuses `ApiCard`'s lifecycle badge and org icons. (UI-01)\n */\nexport function ApiTable({ vms, onSelect }: ApiTableProps) {\n const entityRoute = useRouteRef(entityRouteRef);\n const [sort, setSort] = useState<SortState | null>(null);\n\n const handleSort = (key: SortKey) =>\n setSort(prev =>\n prev?.key === key\n ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' }\n : { key, dir: 'asc' },\n );\n\n const sortedVms = useMemo(() => {\n if (!sort) {\n return vms;\n }\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...vms].sort(\n (a, b) => dir * sortValue(a, sort.key).localeCompare(sortValue(b, sort.key)),\n );\n }, [vms, sort]);\n\n return (\n <div className={styles.tableWrap}>\n <table className={styles.table}>\n <thead>\n <tr>\n <SortableHeader\n label=\"Name\"\n sortKey=\"name\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Lifecycle\"\n sortKey=\"lifecycle\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Version\"\n sortKey=\"version\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Owner\"\n sortKey=\"owner\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Department\"\n sortKey=\"department\"\n sort={sort}\n onSort={handleSort}\n />\n <SortableHeader\n label=\"Orgs\"\n sortKey=\"orgs\"\n sort={sort}\n onSort={handleSort}\n />\n <th scope=\"col\">Spec</th>\n </tr>\n </thead>\n <tbody>\n {sortedVms.map(vm => {\n const { kind, namespace, name } = vm.targetRef;\n const href = entityRoute\n ? entityRoute({ kind: kind.toLowerCase(), namespace, name })\n : undefined;\n return (\n <tr key={vm.targetRef.name}>\n <td className={styles.nameCell}>\n {onSelect ? (\n <button\n type=\"button\"\n className={styles.nameButton}\n onClick={() => onSelect(vm)}\n >\n {vm.name}\n </button>\n ) : href ? (\n <Link to={href} className={styles.nameLink}>\n {vm.name}\n </Link>\n ) : (\n vm.name\n )}\n </td>\n <td>\n <LifecycleBadge kind={vm.badge.kind} label={vm.badge.label} />\n </td>\n <td>{vm.version ?? '—'}</td>\n <td>{vm.ownerLabel}</td>\n <td>{vm.department ?? '—'}</td>\n <td>\n {vm.visibilities.length > 0 ? (\n <span className={styles.orgs}>\n {vm.visibilities.map(visibility => {\n const VisibilityIcon = VISIBILITY_ICON[visibility];\n return (\n <span key={visibility} className={styles.org}>\n <VisibilityIcon aria-hidden size={14} />\n {visibility}\n </span>\n );\n })}\n </span>\n ) : (\n '—'\n )}\n </td>\n <td>\n {vm.hasSpec ? (\n <span\n role=\"img\"\n aria-label=\"OpenAPI spec available\"\n title=\"OpenAPI spec available\"\n className={styles.specIcon}\n >\n <RiFileTextLine aria-hidden size={16} />\n </span>\n ) : (\n '—'\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
2
2
 
3
- var css_248z = ".ApiTable_tableWrap__13d7c249ed {\n width: 100%;\n overflow-x: auto;\n}\n\n.ApiTable_table__13d7c249ed {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--bui-font-size-2);\n}\n\n.ApiTable_table__13d7c249ed th,\n.ApiTable_table__13d7c249ed td {\n padding: var(--bui-space-2) var(--bui-space-3);\n text-align: left;\n vertical-align: middle;\n white-space: nowrap;\n border-bottom: 1px solid var(--bui-border-1);\n}\n\n.ApiTable_table__13d7c249ed th {\n color: var(--bui-fg-secondary);\n font-weight: var(--bui-font-weight-bold);\n}\n\n.ApiTable_table__13d7c249ed td {\n color: var(--bui-fg-secondary);\n}\n\n.ApiTable_table__13d7c249ed tbody tr:hover {\n background-color: var(--bui-bg-neutral-1);\n}\n\n.ApiTable_sortButton__13d7c249ed {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n font-weight: var(--bui-font-weight-bold);\n cursor: pointer;\n}\n\n.ApiTable_sortButton__13d7c249ed:hover {\n color: var(--bui-fg-primary);\n}\n\n.ApiTable_sortButton__13d7c249ed:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n.ApiTable_sortIcon__13d7c249ed {\n color: var(--bui-fg-primary);\n}\n\n.ApiTable_sortIconIdle__13d7c249ed {\n opacity: 0.4;\n}\n\n.ApiTable_nameCell__13d7c249ed {\n color: var(--bui-fg-primary);\n font-weight: var(--bui-font-weight-bold);\n}\n\n.ApiTable_nameLink__13d7c249ed {\n color: inherit;\n text-decoration: none;\n}\n\n.ApiTable_nameLink__13d7c249ed:hover {\n text-decoration: underline;\n}\n\n.ApiTable_orgs__13d7c249ed {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.ApiTable_org__13d7c249ed {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiTable_specIcon__13d7c249ed {\n display: inline-flex;\n color: var(--bui-fg-secondary);\n}\n";
4
- var styles = {"tableWrap":"ApiTable_tableWrap__13d7c249ed","table":"ApiTable_table__13d7c249ed","sortButton":"ApiTable_sortButton__13d7c249ed","sortIcon":"ApiTable_sortIcon__13d7c249ed","sortIconIdle":"ApiTable_sortIconIdle__13d7c249ed","nameCell":"ApiTable_nameCell__13d7c249ed","nameLink":"ApiTable_nameLink__13d7c249ed","orgs":"ApiTable_orgs__13d7c249ed","org":"ApiTable_org__13d7c249ed","specIcon":"ApiTable_specIcon__13d7c249ed"};
3
+ var css_248z = ".ApiTable_tableWrap__f2dd7e5fb8 {\n width: 100%;\n overflow-x: auto;\n}\n\n.ApiTable_table__f2dd7e5fb8 {\n width: 100%;\n border-collapse: collapse;\n font-size: var(--bui-font-size-2);\n}\n\n.ApiTable_table__f2dd7e5fb8 th,\n.ApiTable_table__f2dd7e5fb8 td {\n padding: var(--bui-space-2) var(--bui-space-3);\n text-align: left;\n vertical-align: middle;\n white-space: nowrap;\n border-bottom: 1px solid var(--bui-border-1);\n}\n\n.ApiTable_table__f2dd7e5fb8 th {\n color: var(--bui-fg-secondary);\n font-weight: var(--bui-font-weight-bold);\n}\n\n.ApiTable_table__f2dd7e5fb8 td {\n color: var(--bui-fg-secondary);\n}\n\n.ApiTable_table__f2dd7e5fb8 tbody tr:hover {\n background-color: var(--bui-bg-neutral-1);\n}\n\n.ApiTable_sortButton__f2dd7e5fb8 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n font-weight: var(--bui-font-weight-bold);\n cursor: pointer;\n}\n\n.ApiTable_sortButton__f2dd7e5fb8:hover {\n color: var(--bui-fg-primary);\n}\n\n.ApiTable_sortButton__f2dd7e5fb8:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n.ApiTable_sortIcon__f2dd7e5fb8 {\n color: var(--bui-fg-primary);\n}\n\n.ApiTable_sortIconIdle__f2dd7e5fb8 {\n opacity: 0.4;\n}\n\n.ApiTable_nameCell__f2dd7e5fb8 {\n color: var(--bui-fg-primary);\n font-weight: var(--bui-font-weight-bold);\n}\n\n.ApiTable_nameLink__f2dd7e5fb8 {\n color: inherit;\n text-decoration: none;\n}\n\n.ApiTable_nameLink__f2dd7e5fb8:hover {\n text-decoration: underline;\n}\n\n.ApiTable_nameButton__f2dd7e5fb8 {\n margin: 0;\n padding: 0;\n border: none;\n background: none;\n color: inherit;\n font: inherit;\n font-weight: inherit;\n cursor: pointer;\n}\n\n.ApiTable_nameButton__f2dd7e5fb8:hover {\n text-decoration: underline;\n}\n\n.ApiTable_nameButton__f2dd7e5fb8:focus-visible {\n outline: 2px solid var(--bui-ring);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n.ApiTable_orgs__f2dd7e5fb8 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.ApiTable_org__f2dd7e5fb8 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.ApiTable_specIcon__f2dd7e5fb8 {\n display: inline-flex;\n color: var(--bui-fg-secondary);\n}\n";
4
+ var styles = {"tableWrap":"ApiTable_tableWrap__f2dd7e5fb8","table":"ApiTable_table__f2dd7e5fb8","sortButton":"ApiTable_sortButton__f2dd7e5fb8","sortIcon":"ApiTable_sortIcon__f2dd7e5fb8","sortIconIdle":"ApiTable_sortIconIdle__f2dd7e5fb8","nameCell":"ApiTable_nameCell__f2dd7e5fb8","nameLink":"ApiTable_nameLink__f2dd7e5fb8","nameButton":"ApiTable_nameButton__f2dd7e5fb8","orgs":"ApiTable_orgs__f2dd7e5fb8","org":"ApiTable_org__f2dd7e5fb8","specIcon":"ApiTable_specIcon__f2dd7e5fb8"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { styles as default };
@@ -1,13 +1,14 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { Page, Content, ContentHeader } from '@backstage/core-components';
2
+ import { Page, Content } from '@backstage/core-components';
3
3
  import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
4
  import { EntityListProvider, useEntityList } from '@backstage/plugin-catalog-react';
5
5
  import { ToggleButton } from '@backstage/ui';
6
6
  import { RiLayoutGridLine, RiListUnordered } from '@remixicon/react';
7
- import { useEffect, useMemo, useState } from 'react';
7
+ import { useEffect, useMemo, useState, useCallback } from 'react';
8
8
  import { ANNOTATION_HAS_SPEC } from '../constants.esm.js';
9
9
  import { toApiCardViewModel } from './apiCardViewModel.esm.js';
10
10
  import { ApiCardGrid } from './ApiCardGrid.esm.js';
11
+ import { ApiDetailsDrawer } from './ApiDetailsDrawer.esm.js';
11
12
  import { ApiKindFilter, isApigeeEntity } from './apigeeFacetFilters.esm.js';
12
13
  import { ApigeeOrgFilter } from './apigeeOrgFilter.esm.js';
13
14
  import { isActive } from './apigeeSearchParams.esm.js';
@@ -40,20 +41,6 @@ function ApigeeCatalogContent() {
40
41
  clearAll
41
42
  } = useApigeeFilters();
42
43
  const configApi = useApi(configApiRef);
43
- const overrides = useMemo(() => {
44
- const config = configApi.getOptionalConfig("apigee.catalog.categoryIcons");
45
- if (!config) {
46
- return void 0;
47
- }
48
- const out = {};
49
- for (const key of config.keys()) {
50
- const value = config.getOptionalString(key);
51
- if (value) {
52
- out[key] = value;
53
- }
54
- }
55
- return Object.keys(out).length > 0 ? out : void 0;
56
- }, [configApi]);
57
44
  const orgLabels = useMemo(() => {
58
45
  const config = configApi.getOptionalConfig("apigee.catalog.orgLabels");
59
46
  if (!config) {
@@ -74,8 +61,8 @@ function ApigeeCatalogContent() {
74
61
  );
75
62
  const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);
76
63
  const facets = useMemo(
77
- () => deriveFacets(apigeeEntities, overrides, orgLabels),
78
- [apigeeEntities, overrides, orgLabels]
64
+ () => deriveFacets(apigeeEntities, orgLabels),
65
+ [apigeeEntities, orgLabels]
79
66
  );
80
67
  const counts = useMemo(
81
68
  () => deriveCounts(apigeeEntities, ["org", "lifecycle", "department"], orgLabels),
@@ -89,6 +76,23 @@ function ApigeeCatalogContent() {
89
76
  );
90
77
  const active = isActive(state);
91
78
  const [viewMode, setViewMode] = useState("flat");
79
+ const [selected, setSelected] = useState();
80
+ const entitiesByRef = useMemo(() => {
81
+ const map = /* @__PURE__ */ new Map();
82
+ for (const entity of entities) {
83
+ const namespace = entity.metadata.namespace ?? "default";
84
+ map.set(`${namespace}/${entity.metadata.name}`, entity);
85
+ }
86
+ return map;
87
+ }, [entities]);
88
+ const handleSelect = useCallback(
89
+ (vm) => {
90
+ const { namespace, name } = vm.targetRef;
91
+ setSelected(entitiesByRef.get(`${namespace}/${name}`));
92
+ },
93
+ [entitiesByRef]
94
+ );
95
+ const handleClose = useCallback(() => setSelected(void 0), []);
92
96
  const renderBody = () => {
93
97
  if (error) {
94
98
  return /* @__PURE__ */ jsx(ApiCatalogError, { error });
@@ -99,7 +103,7 @@ function ApigeeCatalogContent() {
99
103
  if (vms.length === 0) {
100
104
  return active ? /* @__PURE__ */ jsx(NoMatchState, { onClearAll: clearAll }) : /* @__PURE__ */ jsx(ApiCatalogEmptyState, {});
101
105
  }
102
- return /* @__PURE__ */ jsx(ApiCardGrid, { vms, viewMode, overrides });
106
+ return /* @__PURE__ */ jsx(ApiCardGrid, { vms, viewMode, onSelect: handleSelect });
103
107
  };
104
108
  const viewToggle = /* @__PURE__ */ jsxs(
105
109
  "div",
@@ -131,36 +135,45 @@ function ApigeeCatalogContent() {
131
135
  ]
132
136
  }
133
137
  );
134
- return /* @__PURE__ */ jsx(Page, { themeId: "apis", children: /* @__PURE__ */ jsxs(Content, { children: [
135
- /* @__PURE__ */ jsx(ContentHeader, { title: "APIs" }),
136
- !loading && !error && apigeeEntities.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
137
- /* @__PURE__ */ jsx(
138
- CountCards,
139
- {
140
- total: apigeeEntities.length,
141
- specCount,
142
- counts,
143
- selected: state.selected,
144
- onSelectFacet: selectOnlyFacet
145
- }
146
- ),
147
- /* @__PURE__ */ jsx(
148
- FilterToolbar,
149
- {
150
- facets,
151
- selected: state.selected,
152
- onToggleFacet: toggleFacet,
153
- onClearFacet: clearFacet,
154
- onClearAll: clearAll,
155
- active,
156
- resultCount: vms.length,
157
- searchBar: /* @__PURE__ */ jsx(SearchBar, { value: inputValue, onChange: setInputValue }),
158
- viewToggle
159
- }
160
- )
161
- ] }) : null,
162
- renderBody()
163
- ] }) });
138
+ return /* @__PURE__ */ jsxs(Page, { themeId: "apis", children: [
139
+ /* @__PURE__ */ jsxs(Content, { children: [
140
+ !loading && !error && apigeeEntities.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
141
+ /* @__PURE__ */ jsx(
142
+ CountCards,
143
+ {
144
+ total: apigeeEntities.length,
145
+ specCount,
146
+ counts,
147
+ selected: state.selected,
148
+ onSelectFacet: selectOnlyFacet
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx(
152
+ FilterToolbar,
153
+ {
154
+ facets,
155
+ selected: state.selected,
156
+ onToggleFacet: toggleFacet,
157
+ onClearFacet: clearFacet,
158
+ onClearAll: clearAll,
159
+ active,
160
+ resultCount: vms.length,
161
+ searchBar: /* @__PURE__ */ jsx(SearchBar, { value: inputValue, onChange: setInputValue }),
162
+ viewToggle
163
+ }
164
+ )
165
+ ] }) : null,
166
+ renderBody()
167
+ ] }),
168
+ /* @__PURE__ */ jsx(
169
+ ApiDetailsDrawer,
170
+ {
171
+ entity: selected,
172
+ orgLabels,
173
+ onClose: handleClose
174
+ }
175
+ )
176
+ ] });
164
177
  }
165
178
  function ApigeeApiCatalogPage() {
166
179
  return /* @__PURE__ */ jsxs(EntityListProvider, { children: [
@@ -1 +1 @@
1
- {"version":3,"file":"ApigeeApiCatalogPage.esm.js","sources":["../../src/components/ApigeeApiCatalogPage.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { Content, ContentHeader, Page } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n EntityListProvider,\n useEntityList,\n type DefaultEntityFilters,\n} from '@backstage/plugin-catalog-react';\nimport { ToggleButton } from '@backstage/ui';\nimport { RiLayoutGridLine, RiListUnordered } from '@remixicon/react';\nimport { useEffect, useMemo, useState } from 'react';\nimport { ANNOTATION_HAS_SPEC } from '../constants';\nimport { toApiCardViewModel } from './apiCardViewModel';\nimport { ApiCardGrid, type ViewMode } from './ApiCardGrid';\nimport {\n ApiKindFilter,\n isApigeeEntity,\n type ApiSearchFilter,\n type FieldFacetFilter,\n} from './apigeeFacetFilters';\nimport { ApigeeOrgFilter } from './apigeeOrgFilter';\nimport { isActive } from './apigeeSearchParams';\nimport { CountCards } from './CountCards';\nimport { deriveCounts, deriveFacets } from './facetModel';\nimport { FilterToolbar } from './FilterToolbar';\nimport { SearchBar } from './SearchBar';\nimport {\n ApiCatalogEmptyState,\n ApiCatalogError,\n NoMatchState,\n SkeletonGrid,\n} from './states';\nimport { useApigeeFilters } from './useApigeeFilters';\n\n/**\n * Entity-list filters registered on the Apigee catalog page. `apigeeKind` (the seam-free\n * `ApiKindFilter`) and `apigeeOrg` (the dual-key is-Apigee gate) are the backend filters applied\n * once by {@link ApplyApigeeFilters}; the facet + search filters\n * (`apigeeLifecycle`/`apigeeOrgFacet`/`apigeeOwner`/`apigeeCategory`/`apigeeDepartment`/`apigeeSearch`)\n * are driven from the URL by `useApigeeFilters`. Exported so the hook can import the filter contract.\n */\nexport type ApigeeFilters = DefaultEntityFilters & {\n apigeeKind?: ApiKindFilter;\n apigeeOrg?: ApigeeOrgFilter;\n apigeeLifecycle?: FieldFacetFilter;\n apigeeOrgFacet?: FieldFacetFilter;\n apigeeOwner?: FieldFacetFilter;\n apigeeCategory?: FieldFacetFilter;\n apigeeDepartment?: FieldFacetFilter;\n apigeeSearch?: ApiSearchFilter;\n};\n\n/**\n * Registers the Apigee backend filters on the shared EntityListProvider ONCE: the seam-free\n * `ApiKindFilter` (kind=api with NO `toQueryValue`, so the provider's URL writer stays a no-op and\n * `useSearchParams` owns a clean URL) plus the dual-key `ApigeeOrgFilter` is-Apigee gate. The facet\n * + search filters are applied separately, URL-driven, by `useApigeeFilters`. Rendered inside the\n * provider; renders nothing itself.\n */\nfunction ApplyApigeeFilters() {\n const { updateFilters } = useEntityList<ApigeeFilters>();\n useEffect(() => {\n updateFilters({\n apigeeKind: new ApiKindFilter(),\n apigeeOrg: new ApigeeOrgFilter(),\n });\n }, [updateFilters]);\n return null;\n}\n\n/**\n * The page body: reads the filtered entity list, maps to view-models, and branches on\n * loading/empty/error. Exposes a Flat/List view toggle (component state only, no view\n * URL param — that is a Phase 6 seam). (UI-01, UI-05, UI-11)\n */\nexport function ApigeeCatalogContent() {\n const { entities, backendEntities, loading, error } =\n useEntityList<ApigeeFilters>();\n const {\n state,\n inputValue,\n setInputValue,\n toggleFacet,\n selectOnlyFacet,\n clearFacet,\n clearAll,\n } = useApigeeFilters();\n const configApi = useApi(configApiRef);\n\n const overrides = useMemo<Record<string, string> | undefined>(() => {\n const config = configApi.getOptionalConfig('apigee.catalog.categoryIcons');\n if (!config) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const key of config.keys()) {\n const value = config.getOptionalString(key);\n if (value) {\n out[key] = value;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n }, [configApi]);\n\n const orgLabels = useMemo<Record<string, string> | undefined>(() => {\n const config = configApi.getOptionalConfig('apigee.catalog.orgLabels');\n if (!config) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const key of config.keys()) {\n const value = config.getOptionalString(key);\n if (value) {\n out[key] = value;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n }, [configApi]);\n\n const apigeeEntities = useMemo(\n () => backendEntities.filter(isApigeeEntity),\n [backendEntities],\n );\n const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);\n const facets = useMemo(\n () => deriveFacets(apigeeEntities, overrides, orgLabels),\n [apigeeEntities, overrides, orgLabels],\n );\n // Static full-catalog counts: the cards stay fixed as filters apply; only the grid narrows.\n const counts = useMemo(\n () => deriveCounts(apigeeEntities, ['org', 'lifecycle', 'department'], orgLabels),\n [apigeeEntities, orgLabels],\n );\n const specCount = useMemo(\n () =>\n apigeeEntities.filter(\n entity => entity.metadata.annotations?.[ANNOTATION_HAS_SPEC] === 'true',\n ).length,\n [apigeeEntities],\n );\n const active = isActive(state);\n const [viewMode, setViewMode] = useState<ViewMode>('flat');\n\n const renderBody = () => {\n if (error) {\n return <ApiCatalogError error={error} />;\n }\n if (loading) {\n return <SkeletonGrid />;\n }\n if (vms.length === 0) {\n return active ? (\n <NoMatchState onClearAll={clearAll} />\n ) : (\n <ApiCatalogEmptyState />\n );\n }\n return <ApiCardGrid vms={vms} viewMode={viewMode} overrides={overrides} />;\n };\n\n const viewToggle = (\n <div\n role=\"group\"\n aria-label=\"API view mode\"\n style={{ display: 'inline-flex', gap: 'var(--bui-space-1)' }}\n >\n <ToggleButton\n aria-label=\"Flat grid view\"\n isSelected={viewMode === 'flat'}\n onChange={() => setViewMode('flat')}\n iconStart={<RiLayoutGridLine />}\n >\n Flat\n </ToggleButton>\n <ToggleButton\n aria-label=\"List view\"\n isSelected={viewMode === 'list'}\n onChange={() => setViewMode('list')}\n iconStart={<RiListUnordered />}\n >\n List\n </ToggleButton>\n </div>\n );\n\n return (\n <Page themeId=\"apis\">\n <Content>\n <ContentHeader title=\"APIs\" />\n {!loading && !error && apigeeEntities.length > 0 ? (\n <>\n <CountCards\n total={apigeeEntities.length}\n specCount={specCount}\n counts={counts}\n selected={state.selected}\n onSelectFacet={selectOnlyFacet}\n />\n <FilterToolbar\n facets={facets}\n selected={state.selected}\n onToggleFacet={toggleFacet}\n onClearFacet={clearFacet}\n onClearAll={clearAll}\n active={active}\n resultCount={vms.length}\n searchBar={\n <SearchBar value={inputValue} onChange={setInputValue} />\n }\n viewToggle={viewToggle}\n />\n </>\n ) : null}\n {renderBody()}\n </Content>\n </Page>\n );\n}\n\n/**\n * Route target for `/apigee` (05-06). Wraps the content in an `EntityListProvider` and\n * registers the Apigee filters — the shared filter context Phase 6's facets + search reuse.\n */\nexport function ApigeeApiCatalogPage() {\n return (\n <EntityListProvider>\n <ApplyApigeeFilters />\n <ApigeeCatalogContent />\n </EntityListProvider>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ApigeeApiCatalogPage.esm.js","sources":["../../src/components/ApigeeApiCatalogPage.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport type { Entity } from '@backstage/catalog-model';\nimport { Content, Page } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n EntityListProvider,\n useEntityList,\n type DefaultEntityFilters,\n} from '@backstage/plugin-catalog-react';\nimport { ToggleButton } from '@backstage/ui';\nimport { RiLayoutGridLine, RiListUnordered } from '@remixicon/react';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { ANNOTATION_HAS_SPEC } from '../constants';\nimport { toApiCardViewModel, type ApiCardViewModel } from './apiCardViewModel';\nimport { ApiCardGrid, type ViewMode } from './ApiCardGrid';\nimport { ApiDetailsDrawer } from './ApiDetailsDrawer';\nimport {\n ApiKindFilter,\n isApigeeEntity,\n type ApiSearchFilter,\n type FieldFacetFilter,\n} from './apigeeFacetFilters';\nimport { ApigeeOrgFilter } from './apigeeOrgFilter';\nimport { isActive } from './apigeeSearchParams';\nimport { CountCards } from './CountCards';\nimport { deriveCounts, deriveFacets } from './facetModel';\nimport { FilterToolbar } from './FilterToolbar';\nimport { SearchBar } from './SearchBar';\nimport {\n ApiCatalogEmptyState,\n ApiCatalogError,\n NoMatchState,\n SkeletonGrid,\n} from './states';\nimport { useApigeeFilters } from './useApigeeFilters';\n\n/**\n * Entity-list filters registered on the Apigee catalog page. `apigeeKind` (the seam-free\n * `ApiKindFilter`) and `apigeeOrg` (the dual-key is-Apigee gate) are the backend filters applied\n * once by {@link ApplyApigeeFilters}; the facet + search filters\n * (`apigeeLifecycle`/`apigeeOrgFacet`/`apigeeOwner`/`apigeeDepartment`/`apigeeSearch`)\n * are driven from the URL by `useApigeeFilters`. Exported so the hook can import the filter contract.\n */\nexport type ApigeeFilters = DefaultEntityFilters & {\n apigeeKind?: ApiKindFilter;\n apigeeOrg?: ApigeeOrgFilter;\n apigeeLifecycle?: FieldFacetFilter;\n apigeeOrgFacet?: FieldFacetFilter;\n apigeeOwner?: FieldFacetFilter;\n apigeeDepartment?: FieldFacetFilter;\n apigeeSearch?: ApiSearchFilter;\n};\n\n/**\n * Registers the Apigee backend filters on the shared EntityListProvider ONCE: the seam-free\n * `ApiKindFilter` (kind=api with NO `toQueryValue`, so the provider's URL writer stays a no-op and\n * `useSearchParams` owns a clean URL) plus the dual-key `ApigeeOrgFilter` is-Apigee gate. The facet\n * + search filters are applied separately, URL-driven, by `useApigeeFilters`. Rendered inside the\n * provider; renders nothing itself.\n */\nfunction ApplyApigeeFilters() {\n const { updateFilters } = useEntityList<ApigeeFilters>();\n useEffect(() => {\n updateFilters({\n apigeeKind: new ApiKindFilter(),\n apigeeOrg: new ApigeeOrgFilter(),\n });\n }, [updateFilters]);\n return null;\n}\n\n/**\n * The page body: reads the filtered entity list, maps to view-models, and branches on\n * loading/empty/error. Exposes a Flat/List view toggle (component state only, no view\n * URL param — that is a Phase 6 seam). (UI-01, UI-05, UI-11)\n */\nexport function ApigeeCatalogContent() {\n const { entities, backendEntities, loading, error } =\n useEntityList<ApigeeFilters>();\n const {\n state,\n inputValue,\n setInputValue,\n toggleFacet,\n selectOnlyFacet,\n clearFacet,\n clearAll,\n } = useApigeeFilters();\n const configApi = useApi(configApiRef);\n\n const orgLabels = useMemo<Record<string, string> | undefined>(() => {\n const config = configApi.getOptionalConfig('apigee.catalog.orgLabels');\n if (!config) {\n return undefined;\n }\n const out: Record<string, string> = {};\n for (const key of config.keys()) {\n const value = config.getOptionalString(key);\n if (value) {\n out[key] = value;\n }\n }\n return Object.keys(out).length > 0 ? out : undefined;\n }, [configApi]);\n\n const apigeeEntities = useMemo(\n () => backendEntities.filter(isApigeeEntity),\n [backendEntities],\n );\n const vms = useMemo(() => entities.map(toApiCardViewModel), [entities]);\n const facets = useMemo(\n () => deriveFacets(apigeeEntities, orgLabels),\n [apigeeEntities, orgLabels],\n );\n // Static full-catalog counts: the cards stay fixed as filters apply; only the grid narrows.\n const counts = useMemo(\n () => deriveCounts(apigeeEntities, ['org', 'lifecycle', 'department'], orgLabels),\n [apigeeEntities, orgLabels],\n );\n const specCount = useMemo(\n () =>\n apigeeEntities.filter(\n entity => entity.metadata.annotations?.[ANNOTATION_HAS_SPEC] === 'true',\n ).length,\n [apigeeEntities],\n );\n const active = isActive(state);\n const [viewMode, setViewMode] = useState<ViewMode>('flat');\n\n // Clicking a card/row opens a details drawer instead of navigating to the catalog\n // entity page. The vm carries only a targetRef, so resolve the full entity by ref.\n const [selected, setSelected] = useState<Entity | undefined>();\n const entitiesByRef = useMemo(() => {\n const map = new Map<string, Entity>();\n for (const entity of entities) {\n const namespace = entity.metadata.namespace ?? 'default';\n map.set(`${namespace}/${entity.metadata.name}`, entity);\n }\n return map;\n }, [entities]);\n const handleSelect = useCallback(\n (vm: ApiCardViewModel) => {\n const { namespace, name } = vm.targetRef;\n setSelected(entitiesByRef.get(`${namespace}/${name}`));\n },\n [entitiesByRef],\n );\n const handleClose = useCallback(() => setSelected(undefined), []);\n\n const renderBody = () => {\n if (error) {\n return <ApiCatalogError error={error} />;\n }\n if (loading) {\n return <SkeletonGrid />;\n }\n if (vms.length === 0) {\n return active ? (\n <NoMatchState onClearAll={clearAll} />\n ) : (\n <ApiCatalogEmptyState />\n );\n }\n return <ApiCardGrid vms={vms} viewMode={viewMode} onSelect={handleSelect} />;\n };\n\n const viewToggle = (\n <div\n role=\"group\"\n aria-label=\"API view mode\"\n style={{ display: 'inline-flex', gap: 'var(--bui-space-1)' }}\n >\n <ToggleButton\n aria-label=\"Flat grid view\"\n isSelected={viewMode === 'flat'}\n onChange={() => setViewMode('flat')}\n iconStart={<RiLayoutGridLine />}\n >\n Flat\n </ToggleButton>\n <ToggleButton\n aria-label=\"List view\"\n isSelected={viewMode === 'list'}\n onChange={() => setViewMode('list')}\n iconStart={<RiListUnordered />}\n >\n List\n </ToggleButton>\n </div>\n );\n\n return (\n <Page themeId=\"apis\">\n <Content>\n {!loading && !error && apigeeEntities.length > 0 ? (\n <>\n <CountCards\n total={apigeeEntities.length}\n specCount={specCount}\n counts={counts}\n selected={state.selected}\n onSelectFacet={selectOnlyFacet}\n />\n <FilterToolbar\n facets={facets}\n selected={state.selected}\n onToggleFacet={toggleFacet}\n onClearFacet={clearFacet}\n onClearAll={clearAll}\n active={active}\n resultCount={vms.length}\n searchBar={\n <SearchBar value={inputValue} onChange={setInputValue} />\n }\n viewToggle={viewToggle}\n />\n </>\n ) : null}\n {renderBody()}\n </Content>\n <ApiDetailsDrawer\n entity={selected}\n orgLabels={orgLabels}\n onClose={handleClose}\n />\n </Page>\n );\n}\n\n/**\n * Route target for `/apigee` (05-06). Wraps the content in an `EntityListProvider` and\n * registers the Apigee filters — the shared filter context Phase 6's facets + search reuse.\n */\nexport function ApigeeApiCatalogPage() {\n return (\n <EntityListProvider>\n <ApplyApigeeFilters />\n <ApigeeCatalogContent />\n </EntityListProvider>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useState, useRef, useEffect } from 'react';
2
+ import { useState, useCallback, useRef, useEffect } from 'react';
3
3
  import { Button, Checkbox } from '@backstage/ui';
4
4
  import { RiCloseLine, RiArrowDropDownLine } from '@remixicon/react';
5
5
  import styles from './FilterToolbar.module.css.esm.js';
@@ -7,25 +7,27 @@ import styles from './FilterToolbar.module.css.esm.js';
7
7
  function FacetDropdown({
8
8
  facet,
9
9
  selected,
10
+ isOpen,
11
+ onOpenToggle,
12
+ onClose,
10
13
  onToggle,
11
14
  onClear
12
15
  }) {
13
- const [open, setOpen] = useState(false);
14
16
  const ref = useRef(null);
15
17
  const menuId = `facet-menu-${facet.key}`;
16
18
  const count = selected.length;
17
19
  useEffect(() => {
18
- if (!open) {
20
+ if (!isOpen) {
19
21
  return void 0;
20
22
  }
21
23
  const onDocMouseDown = (event) => {
22
24
  if (ref.current && !ref.current.contains(event.target)) {
23
- setOpen(false);
25
+ onClose();
24
26
  }
25
27
  };
26
28
  const onKeyDown = (event) => {
27
29
  if (event.key === "Escape") {
28
- setOpen(false);
30
+ onClose();
29
31
  }
30
32
  };
31
33
  document.addEventListener("mousedown", onDocMouseDown);
@@ -34,7 +36,7 @@ function FacetDropdown({
34
36
  document.removeEventListener("mousedown", onDocMouseDown);
35
37
  document.removeEventListener("keydown", onKeyDown);
36
38
  };
37
- }, [open]);
39
+ }, [isOpen, onClose]);
38
40
  return /* @__PURE__ */ jsxs("div", { ref, className: styles.facet, children: [
39
41
  /* @__PURE__ */ jsxs(
40
42
  Button,
@@ -44,17 +46,17 @@ function FacetDropdown({
44
46
  isDisabled: !facet.enabled,
45
47
  iconEnd: /* @__PURE__ */ jsx(RiArrowDropDownLine, {}),
46
48
  "aria-haspopup": "true",
47
- "aria-expanded": open,
48
- "aria-controls": open ? menuId : void 0,
49
+ "aria-expanded": isOpen,
50
+ "aria-controls": isOpen ? menuId : void 0,
49
51
  "aria-label": count > 0 ? `${facet.label} filter, ${count} selected` : `${facet.label} filter`,
50
- onPress: () => setOpen((prev) => !prev),
52
+ onPress: onOpenToggle,
51
53
  children: [
52
54
  facet.label,
53
55
  count > 0 ? /* @__PURE__ */ jsx("span", { className: styles.countChip, children: count }) : null
54
56
  ]
55
57
  }
56
58
  ),
57
- open ? /* @__PURE__ */ jsxs(
59
+ isOpen ? /* @__PURE__ */ jsxs(
58
60
  "div",
59
61
  {
60
62
  id: menuId,
@@ -62,22 +64,16 @@ function FacetDropdown({
62
64
  role: "group",
63
65
  "aria-label": `${facet.label} options`,
64
66
  children: [
65
- facet.options.map((option) => {
66
- const OptionIcon = facet.icons?.[option.value];
67
- return /* @__PURE__ */ jsx(
68
- Checkbox,
69
- {
70
- className: styles.option,
71
- isSelected: selected.includes(option.value),
72
- onChange: () => onToggle(option.value),
73
- children: OptionIcon ? /* @__PURE__ */ jsxs("span", { className: styles.optionLabel, children: [
74
- /* @__PURE__ */ jsx(OptionIcon, { "aria-hidden": true, size: 16 }),
75
- option.label
76
- ] }) : option.label
77
- },
78
- option.value
79
- );
80
- }),
67
+ facet.options.map((option) => /* @__PURE__ */ jsx(
68
+ Checkbox,
69
+ {
70
+ className: styles.option,
71
+ isSelected: selected.includes(option.value),
72
+ onChange: () => onToggle(option.value),
73
+ children: option.label
74
+ },
75
+ option.value
76
+ )),
81
77
  count > 0 ? /* @__PURE__ */ jsx("div", { className: styles.menuFooter, children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "small", onPress: onClear, children: "Clear" }) }) : null
82
78
  ]
83
79
  }
@@ -95,6 +91,8 @@ function FilterToolbar({
95
91
  searchBar,
96
92
  viewToggle
97
93
  }) {
94
+ const [openKey, setOpenKey] = useState(null);
95
+ const closeMenus = useCallback(() => setOpenKey(null), []);
98
96
  return /* @__PURE__ */ jsxs("div", { role: "search", "aria-label": "Filter and search APIs", children: [
99
97
  /* @__PURE__ */ jsxs("div", { className: styles.toolbar, children: [
100
98
  facets.map((facet) => /* @__PURE__ */ jsx(
@@ -102,6 +100,9 @@ function FilterToolbar({
102
100
  {
103
101
  facet,
104
102
  selected: selected[facet.key],
103
+ isOpen: openKey === facet.key,
104
+ onOpenToggle: () => setOpenKey((prev) => prev === facet.key ? null : facet.key),
105
+ onClose: closeMenus,
105
106
  onToggle: (value) => onToggleFacet(facet.key, value),
106
107
  onClear: () => onClearFacet(facet.key)
107
108
  },
@@ -1 +1 @@
1
- {"version":3,"file":"FilterToolbar.esm.js","sources":["../../src/components/FilterToolbar.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button, Checkbox } from '@backstage/ui';\nimport { RiArrowDropDownLine, RiCloseLine } from '@remixicon/react';\nimport type { DerivedFacet, FacetKey } from './facetModel';\nimport styles from './FilterToolbar.module.css';\n\nexport interface FilterToolbarProps {\n /** Derived facets from `deriveFacets()`; ALL render (revised D-05) — one with no values is disabled. */\n facets: DerivedFacet[];\n /** Current selections per facet (hydrated from the URL state in 06-04). */\n selected: Record<FacetKey, string[]>;\n onToggleFacet: (key: FacetKey, value: string) => void;\n /** Per-facet menu-footer \"Clear\" (clears just that facet). */\n onClearFacet: (key: FacetKey) => void;\n /** Toolbar \"Clear all\" (resets every facet + search). */\n onClearAll: () => void;\n /** `isActive(state)` — gates the \"Clear all\" affordance. */\n active: boolean;\n /** Filtered result count for the polite live region (UI-07). */\n resultCount: number;\n /** `SearchBar` mounted inline in the toolbar row. */\n searchBar: ReactNode;\n /** View-mode toggle (Flat/List) mounted at the right of the toolbar row. */\n viewToggle?: ReactNode;\n}\n\n/**\n * A single multi-select facet as an outlined disclosure button. Options are @backstage/ui\n * `Checkbox`es in a popover that stays OPEN while toggling (multi-select); the popover closes on\n * outside-click or Escape. All dynamic text (`facet.label`, `option.label`) renders as React\n * children only — never raw HTML (T-06-03-01).\n */\nfunction FacetDropdown({\n facet,\n selected,\n onToggle,\n onClear,\n}: {\n facet: DerivedFacet;\n selected: string[];\n onToggle: (value: string) => void;\n onClear: () => void;\n}) {\n const [open, setOpen] = useState(false);\n const ref = useRef<HTMLDivElement>(null);\n const menuId = `facet-menu-${facet.key}`;\n const count = selected.length;\n\n useEffect(() => {\n if (!open) {\n return undefined;\n }\n const onDocMouseDown = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n setOpen(false);\n }\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setOpen(false);\n }\n };\n document.addEventListener('mousedown', onDocMouseDown);\n document.addEventListener('keydown', onKeyDown);\n return () => {\n document.removeEventListener('mousedown', onDocMouseDown);\n document.removeEventListener('keydown', onKeyDown);\n };\n }, [open]);\n\n return (\n <div ref={ref} className={styles.facet}>\n <Button\n variant={count > 0 ? 'primary' : 'secondary'}\n size=\"small\"\n isDisabled={!facet.enabled}\n iconEnd={<RiArrowDropDownLine />}\n aria-haspopup=\"true\"\n aria-expanded={open}\n aria-controls={open ? menuId : undefined}\n aria-label={\n count > 0\n ? `${facet.label} filter, ${count} selected`\n : `${facet.label} filter`\n }\n onPress={() => setOpen(prev => !prev)}\n >\n {facet.label}\n {count > 0 ? <span className={styles.countChip}>{count}</span> : null}\n </Button>\n {open ? (\n <div\n id={menuId}\n className={styles.menu}\n role=\"group\"\n aria-label={`${facet.label} options`}\n >\n {facet.options.map(option => {\n const OptionIcon = facet.icons?.[option.value];\n return (\n <Checkbox\n key={option.value}\n className={styles.option}\n isSelected={selected.includes(option.value)}\n onChange={() => onToggle(option.value)}\n >\n {OptionIcon ? (\n <span className={styles.optionLabel}>\n <OptionIcon aria-hidden size={16} />\n {option.label}\n </span>\n ) : (\n option.label\n )}\n </Checkbox>\n );\n })}\n {count > 0 ? (\n <div className={styles.menuFooter}>\n <Button variant=\"tertiary\" size=\"small\" onPress={onClear}>\n Clear\n </Button>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Presentational Phase 6 toolbar (D-01 top toolbar, D-02 multi-select dropdowns). Renders EVERY\n * facet in `FACET_ORDER` (revised D-05 — a facet with no values renders disabled) followed on the\n * same row by the inline `searchBar` paired with the `viewToggle` (and a conditional \"Clear all\"),\n * plus a polite `aria-live` result-count region. Purely props-driven — no data fetching and no\n * URL/provider access (that wiring is 06-04). (UI-06, UI-07)\n */\nexport function FilterToolbar({\n facets,\n selected,\n onToggleFacet,\n onClearFacet,\n onClearAll,\n active,\n resultCount,\n searchBar,\n viewToggle,\n}: FilterToolbarProps) {\n return (\n <div role=\"search\" aria-label=\"Filter and search APIs\">\n <div className={styles.toolbar}>\n {facets.map(facet => (\n <FacetDropdown\n key={facet.key}\n facet={facet}\n selected={selected[facet.key]}\n onToggle={value => onToggleFacet(facet.key, value)}\n onClear={() => onClearFacet(facet.key)}\n />\n ))}\n <div className={styles.searchRow}>\n {searchBar}\n {active ? (\n <Button\n variant=\"tertiary\"\n size=\"small\"\n iconStart={<RiCloseLine />}\n onPress={onClearAll}\n >\n Clear all\n </Button>\n ) : null}\n {viewToggle}\n </div>\n </div>\n <span className={styles.srOnly} aria-live=\"polite\">\n {`${resultCount} APIs match your filters`}\n </span>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FilterToolbar.esm.js","sources":["../../src/components/FilterToolbar.tsx"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Button, Checkbox } from '@backstage/ui';\nimport { RiArrowDropDownLine, RiCloseLine } from '@remixicon/react';\nimport type { DerivedFacet, FacetKey } from './facetModel';\nimport styles from './FilterToolbar.module.css';\n\nexport interface FilterToolbarProps {\n /** Derived facets from `deriveFacets()`; ALL render (revised D-05) — one with no values is disabled. */\n facets: DerivedFacet[];\n /** Current selections per facet (hydrated from the URL state in 06-04). */\n selected: Record<FacetKey, string[]>;\n onToggleFacet: (key: FacetKey, value: string) => void;\n /** Per-facet menu-footer \"Clear\" (clears just that facet). */\n onClearFacet: (key: FacetKey) => void;\n /** Toolbar \"Clear all\" (resets every facet + search). */\n onClearAll: () => void;\n /** `isActive(state)` — gates the \"Clear all\" affordance. */\n active: boolean;\n /** Filtered result count for the polite live region (UI-07). */\n resultCount: number;\n /** `SearchBar` mounted inline in the toolbar row. */\n searchBar: ReactNode;\n /** View-mode toggle (Flat/List) mounted at the right of the toolbar row. */\n viewToggle?: ReactNode;\n}\n\n/**\n * A single multi-select facet as an outlined disclosure button. Options are @backstage/ui\n * `Checkbox`es in a popover that stays OPEN while toggling (multi-select); the popover closes on\n * outside-click or Escape. All dynamic text (`facet.label`, `option.label`) renders as React\n * children only — never raw HTML (T-06-03-01).\n */\nfunction FacetDropdown({\n facet,\n selected,\n isOpen,\n onOpenToggle,\n onClose,\n onToggle,\n onClear,\n}: {\n facet: DerivedFacet;\n selected: string[];\n isOpen: boolean;\n onOpenToggle: () => void;\n onClose: () => void;\n onToggle: (value: string) => void;\n onClear: () => void;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n const menuId = `facet-menu-${facet.key}`;\n const count = selected.length;\n\n useEffect(() => {\n if (!isOpen) {\n return undefined;\n }\n const onDocMouseDown = (event: MouseEvent) => {\n if (ref.current && !ref.current.contains(event.target as Node)) {\n onClose();\n }\n };\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onClose();\n }\n };\n document.addEventListener('mousedown', onDocMouseDown);\n document.addEventListener('keydown', onKeyDown);\n return () => {\n document.removeEventListener('mousedown', onDocMouseDown);\n document.removeEventListener('keydown', onKeyDown);\n };\n }, [isOpen, onClose]);\n\n return (\n <div ref={ref} className={styles.facet}>\n <Button\n variant={count > 0 ? 'primary' : 'secondary'}\n size=\"small\"\n isDisabled={!facet.enabled}\n iconEnd={<RiArrowDropDownLine />}\n aria-haspopup=\"true\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n aria-label={\n count > 0\n ? `${facet.label} filter, ${count} selected`\n : `${facet.label} filter`\n }\n onPress={onOpenToggle}\n >\n {facet.label}\n {count > 0 ? <span className={styles.countChip}>{count}</span> : null}\n </Button>\n {isOpen ? (\n <div\n id={menuId}\n className={styles.menu}\n role=\"group\"\n aria-label={`${facet.label} options`}\n >\n {facet.options.map(option => (\n <Checkbox\n key={option.value}\n className={styles.option}\n isSelected={selected.includes(option.value)}\n onChange={() => onToggle(option.value)}\n >\n {option.label}\n </Checkbox>\n ))}\n {count > 0 ? (\n <div className={styles.menuFooter}>\n <Button variant=\"tertiary\" size=\"small\" onPress={onClear}>\n Clear\n </Button>\n </div>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * Presentational Phase 6 toolbar (D-01 top toolbar, D-02 multi-select dropdowns). Renders EVERY\n * facet in `FACET_ORDER` (revised D-05 — a facet with no values renders disabled) followed on the\n * same row by the inline `searchBar` paired with the `viewToggle` (and a conditional \"Clear all\"),\n * plus a polite `aria-live` result-count region. Purely props-driven — no data fetching and no\n * URL/provider access (that wiring is 06-04). (UI-06, UI-07)\n */\nexport function FilterToolbar({\n facets,\n selected,\n onToggleFacet,\n onClearFacet,\n onClearAll,\n active,\n resultCount,\n searchBar,\n viewToggle,\n}: FilterToolbarProps) {\n // Only one facet menu open at a time — opening another closes the previous.\n const [openKey, setOpenKey] = useState<FacetKey | null>(null);\n const closeMenus = useCallback(() => setOpenKey(null), []);\n\n return (\n <div role=\"search\" aria-label=\"Filter and search APIs\">\n <div className={styles.toolbar}>\n {facets.map(facet => (\n <FacetDropdown\n key={facet.key}\n facet={facet}\n selected={selected[facet.key]}\n isOpen={openKey === facet.key}\n onOpenToggle={() =>\n setOpenKey(prev => (prev === facet.key ? null : facet.key))\n }\n onClose={closeMenus}\n onToggle={value => onToggleFacet(facet.key, value)}\n onClear={() => onClearFacet(facet.key)}\n />\n ))}\n <div className={styles.searchRow}>\n {searchBar}\n {active ? (\n <Button\n variant=\"tertiary\"\n size=\"small\"\n iconStart={<RiCloseLine />}\n onPress={onClearAll}\n >\n Clear all\n </Button>\n ) : null}\n {viewToggle}\n </div>\n </div>\n <span className={styles.srOnly} aria-live=\"polite\">\n {`${resultCount} APIs match your filters`}\n </span>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import styleInject from '../node_modules_dist/style-inject/dist/style-inject.es.esm.js';
2
2
 
3
- var css_248z = ".FilterToolbar_toolbar__0a37ccc2c0 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-2);\n margin-bottom: var(--bui-space-4);\n}\n\n.FilterToolbar_facet__0a37ccc2c0 {\n position: relative;\n}\n\n.FilterToolbar_countChip__0a37ccc2c0 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n margin-left: var(--bui-space-1);\n padding: 0 var(--bui-space-1);\n border-radius: var(--bui-radius-full);\n background-color: var(--bui-bg-solid);\n color: var(--bui-fg-solid);\n font-size: var(--bui-font-size-1);\n font-variant-numeric: tabular-nums;\n}\n\n.FilterToolbar_menu__0a37ccc2c0 {\n position: absolute;\n top: calc(100% + var(--bui-space-1));\n left: 0;\n z-index: 10;\n min-width: 200px;\n padding: var(--bui-space-2) 0;\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-app);\n box-shadow: var(--bui-shadow);\n}\n\n.FilterToolbar_option__0a37ccc2c0 {\n padding: var(--bui-space-1) var(--bui-space-3);\n}\n\n.FilterToolbar_optionLabel__0a37ccc2c0 {\n display: inline-flex;\n align-items: center;\n gap: var(--bui-space-1);\n}\n\n.FilterToolbar_menuFooter__0a37ccc2c0 {\n display: flex;\n justify-content: flex-end;\n padding: var(--bui-space-1) var(--bui-space-2) 0;\n}\n\n.FilterToolbar_searchRow__0a37ccc2c0 {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.FilterToolbar_srOnly__0a37ccc2c0 {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n";
4
- var styles = {"toolbar":"FilterToolbar_toolbar__0a37ccc2c0","facet":"FilterToolbar_facet__0a37ccc2c0","countChip":"FilterToolbar_countChip__0a37ccc2c0","menu":"FilterToolbar_menu__0a37ccc2c0","option":"FilterToolbar_option__0a37ccc2c0","optionLabel":"FilterToolbar_optionLabel__0a37ccc2c0","menuFooter":"FilterToolbar_menuFooter__0a37ccc2c0","searchRow":"FilterToolbar_searchRow__0a37ccc2c0","srOnly":"FilterToolbar_srOnly__0a37ccc2c0"};
3
+ var css_248z = ".FilterToolbar_toolbar__56038ba351 {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--bui-space-2);\n margin-bottom: var(--bui-space-4);\n}\n\n.FilterToolbar_facet__56038ba351 {\n position: relative;\n}\n\n.FilterToolbar_countChip__56038ba351 {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n margin-left: var(--bui-space-1);\n padding: 0 var(--bui-space-1);\n border-radius: var(--bui-radius-full);\n background-color: var(--bui-bg-solid);\n color: var(--bui-fg-solid);\n font-size: var(--bui-font-size-1);\n font-variant-numeric: tabular-nums;\n}\n\n.FilterToolbar_menu__56038ba351 {\n position: absolute;\n top: calc(100% + var(--bui-space-1));\n left: 0;\n z-index: 10;\n min-width: 200px;\n padding: var(--bui-space-2) 0;\n border: 1px solid var(--bui-border-1);\n border-radius: var(--bui-radius-3);\n background-color: var(--bui-bg-app);\n box-shadow: var(--bui-shadow);\n}\n\n.FilterToolbar_option__56038ba351 {\n padding: var(--bui-space-1) var(--bui-space-3);\n}\n\n.FilterToolbar_menuFooter__56038ba351 {\n display: flex;\n justify-content: flex-end;\n padding: var(--bui-space-1) var(--bui-space-2) 0;\n}\n\n.FilterToolbar_searchRow__56038ba351 {\n flex: 1 1 auto;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: var(--bui-space-2);\n}\n\n.FilterToolbar_srOnly__56038ba351 {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n border: 0;\n}\n";
4
+ var styles = {"toolbar":"FilterToolbar_toolbar__56038ba351","facet":"FilterToolbar_facet__56038ba351","countChip":"FilterToolbar_countChip__56038ba351","menu":"FilterToolbar_menu__56038ba351","option":"FilterToolbar_option__56038ba351","menuFooter":"FilterToolbar_menuFooter__56038ba351","searchRow":"FilterToolbar_searchRow__56038ba351","srOnly":"FilterToolbar_srOnly__56038ba351"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { styles as default };
@@ -1,6 +1,6 @@
1
1
  import { parseEntityRef } from '@backstage/catalog-model';
2
2
  import { humanizeEntityRef } from '@backstage/plugin-catalog-react';
3
- import { ANNOTATION_CATEGORY, ANNOTATION_VERSION, ANNOTATION_PROXY_NAME, ANNOTATION_HAS_SPEC, LABEL_DEPARTMENT, ANNOTATION_APIGEE_ORGS, ANNOTATION_APIGEE_ORG } from '../constants.esm.js';
3
+ import { ANNOTATION_VERSION, ANNOTATION_PROXY_NAME, ANNOTATION_HAS_SPEC, LABEL_DEPARTMENT, ANNOTATION_APIGEE_ORGS, ANNOTATION_APIGEE_ORG } from '../constants.esm.js';
4
4
 
5
5
  function toBadge(lifecycle) {
6
6
  switch (lifecycle) {
@@ -41,7 +41,6 @@ function toApiCardViewModel(entity) {
41
41
  badge: toBadge(entity.spec?.lifecycle),
42
42
  hasSpec: annotations[ANNOTATION_HAS_SPEC] === "true",
43
43
  version: annotations[ANNOTATION_VERSION] || versionFromProxyName(annotations[ANNOTATION_PROXY_NAME]) || void 0,
44
- category: annotations[ANNOTATION_CATEGORY]?.split(",")[0]?.trim() || void 0,
45
44
  description,
46
45
  targetRef: {
47
46
  kind: "API",
@@ -1 +1 @@
1
- {"version":3,"file":"apiCardViewModel.esm.js","sources":["../../src/components/apiCardViewModel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { type Entity, parseEntityRef } from '@backstage/catalog-model';\nimport { humanizeEntityRef } from '@backstage/plugin-catalog-react';\nimport {\n ANNOTATION_HAS_SPEC,\n ANNOTATION_CATEGORY,\n ANNOTATION_VERSION,\n ANNOTATION_PROXY_NAME,\n ANNOTATION_APIGEE_ORGS,\n ANNOTATION_APIGEE_ORG,\n LABEL_DEPARTMENT,\n} from '../constants';\n\nexport type LifecycleBadgeKind = 'ok' | 'warning' | 'error';\n\n/** Apigee org exposure shown on the card, derived from the org name. */\nexport type ApiVisibility = 'private' | 'public';\n\nexport interface ApiCardViewModel {\n /** metadata.title ?? metadata.name */\n name: string;\n /** humanizeEntityRef(spec.owner, { defaultKind: 'group' }) — 'unknown' when absent */\n ownerLabel: string;\n /** metadata.labels.department — omitted when absent */\n department?: string;\n /** Exposure derived from apigee.com/orgs — 'private', 'public', or both; [] when none. */\n visibilities: ApiVisibility[];\n badge: { label: string; kind: LifecycleBadgeKind };\n /** annotations['apigee.com/has-spec'] === 'true' */\n hasSpec: boolean;\n /** annotations['apigee.com/version'], else the proxy name's -v<major> as x.0.0 — omitted when absent (D-05) */\n version?: string;\n /** first comma-separated slug of apigee.com/category — undefined => 'Other' bucket */\n category?: string;\n /** metadata.description — omitted when empty */\n description?: string;\n /** for entityRouteRef navigation (05-04) */\n targetRef: { kind: 'API'; namespace: string; name: string };\n}\n\n/** D-04 lifecycle -> badge mapping. Unknown/undeployed/missing default to warning. */\nfunction toBadge(lifecycle: string | undefined): {\n label: string;\n kind: LifecycleBadgeKind;\n} {\n switch (lifecycle) {\n case 'production':\n return { label: 'Production', kind: 'ok' };\n case 'deprecated':\n return { label: 'Deprecated', kind: 'error' };\n case 'experimental':\n return { label: 'Experimental', kind: 'warning' };\n default:\n return { label: 'Unknown', kind: 'warning' };\n }\n}\n\n/** Derive exposure from the Apigee org annotation(s). Org names follow\n * `apigee-x-{private|public}-p-<id>`; an API can be published to one or both. */\nfunction toVisibilities(annotations: Record<string, string>): ApiVisibility[] {\n const raw =\n annotations[ANNOTATION_APIGEE_ORGS] ?? annotations[ANNOTATION_APIGEE_ORG] ?? '';\n const orgs = raw\n .split(',')\n .map(org => org.trim().toLowerCase())\n .filter(Boolean);\n const visibilities: ApiVisibility[] = [];\n if (orgs.some(org => org.includes('private'))) visibilities.push('private');\n if (orgs.some(org => org.includes('public'))) visibilities.push('public');\n return visibilities;\n}\n\n/** Extract a semantic version from a proxy name's trailing `-v<major>` segment\n * (e.g. `...-v4` -> `4.0.0`). Undefined when the name has no version suffix. */\nfunction versionFromProxyName(proxyName: string | undefined): string | undefined {\n const match = proxyName?.match(/-v(\\d+)$/i);\n return match ? `${Number(match[1])}.0.0` : undefined;\n}\n\n/** PURE mapper: an Apigee `kind: API` entity -> the view-model each tile renders. */\nexport function toApiCardViewModel(entity: Entity): ApiCardViewModel {\n const annotations = entity.metadata.annotations ?? {};\n const owner = entity.spec?.owner as string | undefined;\n const description = entity.metadata.description?.trim()\n ? entity.metadata.description\n : undefined;\n\n return {\n name: entity.metadata.title ?? entity.metadata.name,\n ownerLabel: owner\n ? humanizeEntityRef(\n parseEntityRef(owner, { defaultKind: 'group', defaultNamespace: 'default' }),\n { defaultKind: 'group' },\n )\n : 'unknown',\n department: entity.metadata.labels?.[LABEL_DEPARTMENT] || undefined,\n visibilities: toVisibilities(annotations),\n badge: toBadge(entity.spec?.lifecycle as string | undefined),\n hasSpec: annotations[ANNOTATION_HAS_SPEC] === 'true',\n version:\n annotations[ANNOTATION_VERSION] ||\n versionFromProxyName(annotations[ANNOTATION_PROXY_NAME]) ||\n undefined,\n category: annotations[ANNOTATION_CATEGORY]?.split(',')[0]?.trim() || undefined,\n description,\n targetRef: {\n kind: 'API',\n namespace: entity.metadata.namespace ?? 'default',\n name: entity.metadata.name,\n },\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apiCardViewModel.esm.js","sources":["../../src/components/apiCardViewModel.ts"],"sourcesContent":["// SPDX-License-Identifier: Apache-2.0\nimport { type Entity, parseEntityRef } from '@backstage/catalog-model';\nimport { humanizeEntityRef } from '@backstage/plugin-catalog-react';\nimport {\n ANNOTATION_HAS_SPEC,\n ANNOTATION_VERSION,\n ANNOTATION_PROXY_NAME,\n ANNOTATION_APIGEE_ORGS,\n ANNOTATION_APIGEE_ORG,\n LABEL_DEPARTMENT,\n} from '../constants';\n\nexport type LifecycleBadgeKind = 'ok' | 'warning' | 'error';\n\n/** Apigee org exposure shown on the card, derived from the org name. */\nexport type ApiVisibility = 'private' | 'public';\n\nexport interface ApiCardViewModel {\n /** metadata.title ?? metadata.name */\n name: string;\n /** humanizeEntityRef(spec.owner, { defaultKind: 'group' }) — 'unknown' when absent */\n ownerLabel: string;\n /** metadata.labels.department — omitted when absent */\n department?: string;\n /** Exposure derived from apigee.com/orgs — 'private', 'public', or both; [] when none. */\n visibilities: ApiVisibility[];\n badge: { label: string; kind: LifecycleBadgeKind };\n /** annotations['apigee.com/has-spec'] === 'true' */\n hasSpec: boolean;\n /** annotations['apigee.com/version'], else the proxy name's -v<major> as x.0.0 — omitted when absent (D-05) */\n version?: string;\n /** metadata.description — omitted when empty */\n description?: string;\n /** for entityRouteRef navigation (05-04) */\n targetRef: { kind: 'API'; namespace: string; name: string };\n}\n\n/** D-04 lifecycle -> badge mapping. Unknown/undeployed/missing default to warning. */\nfunction toBadge(lifecycle: string | undefined): {\n label: string;\n kind: LifecycleBadgeKind;\n} {\n switch (lifecycle) {\n case 'production':\n return { label: 'Production', kind: 'ok' };\n case 'deprecated':\n return { label: 'Deprecated', kind: 'error' };\n case 'experimental':\n return { label: 'Experimental', kind: 'warning' };\n default:\n return { label: 'Unknown', kind: 'warning' };\n }\n}\n\n/** Derive exposure from the Apigee org annotation(s). Org names follow\n * `apigee-x-{private|public}-p-<id>`; an API can be published to one or both. */\nfunction toVisibilities(annotations: Record<string, string>): ApiVisibility[] {\n const raw =\n annotations[ANNOTATION_APIGEE_ORGS] ?? annotations[ANNOTATION_APIGEE_ORG] ?? '';\n const orgs = raw\n .split(',')\n .map(org => org.trim().toLowerCase())\n .filter(Boolean);\n const visibilities: ApiVisibility[] = [];\n if (orgs.some(org => org.includes('private'))) visibilities.push('private');\n if (orgs.some(org => org.includes('public'))) visibilities.push('public');\n return visibilities;\n}\n\n/** Extract a semantic version from a proxy name's trailing `-v<major>` segment\n * (e.g. `...-v4` -> `4.0.0`). Undefined when the name has no version suffix. */\nfunction versionFromProxyName(proxyName: string | undefined): string | undefined {\n const match = proxyName?.match(/-v(\\d+)$/i);\n return match ? `${Number(match[1])}.0.0` : undefined;\n}\n\n/** PURE mapper: an Apigee `kind: API` entity -> the view-model each tile renders. */\nexport function toApiCardViewModel(entity: Entity): ApiCardViewModel {\n const annotations = entity.metadata.annotations ?? {};\n const owner = entity.spec?.owner as string | undefined;\n const description = entity.metadata.description?.trim()\n ? entity.metadata.description\n : undefined;\n\n return {\n name: entity.metadata.title ?? entity.metadata.name,\n ownerLabel: owner\n ? humanizeEntityRef(\n parseEntityRef(owner, { defaultKind: 'group', defaultNamespace: 'default' }),\n { defaultKind: 'group' },\n )\n : 'unknown',\n department: entity.metadata.labels?.[LABEL_DEPARTMENT] || undefined,\n visibilities: toVisibilities(annotations),\n badge: toBadge(entity.spec?.lifecycle as string | undefined),\n hasSpec: annotations[ANNOTATION_HAS_SPEC] === 'true',\n version:\n annotations[ANNOTATION_VERSION] ||\n versionFromProxyName(annotations[ANNOTATION_PROXY_NAME]) ||\n undefined,\n description,\n targetRef: {\n kind: 'API',\n namespace: entity.metadata.namespace ?? 'default',\n name: entity.metadata.name,\n },\n };\n}\n"],"names":[],"mappings":"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"}