Merge branch 'backstage:master' into refine-custom-permissions-doc

This commit is contained in:
Stanley Nicholl
2023-12-27 11:04:34 +00:00
committed by GitHub
964 changed files with 21355 additions and 5051 deletions
@@ -0,0 +1,314 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="732px" height="462px" viewBox="-0.5 -0.5 732 462" content="&lt;mxfile scale=&quot;1.5&quot; border=&quot;20&quot;&gt;&lt;diagram id=&quot;gKBvn7eBFHudv9VMIRD-&quot; name=&quot;Page-1&quot;&gt;7VtZc6M4EP41foxL3PBoO8nMVu2RqtmtnTwqINuqYMTK8jW/fgVIgBC+wUlm4qmaQg1qpD6+PkQG1mSx/UJhOv+DRCgemCDaDqz7gWnajsP/zwi7guAaQUGYURwVJKMifMM/kCACQV3hCC2VBxkhMcOpSgxJkqCQKTRIKdmoj01JrL41hTOkEb6FMNap/+KIzcW2fFDRvyI8m8s3O+LGCwxfZ5SsEvG6gWlN819xewElK/H8cg4jsqmRrIeBNaGEsOJqsZ2gOJOslFox73HP3XLZFCXslAlmMWEN45XY+ShNxdLYTkoj3xDKpoCBNd7MMUPfUhhmdzdc/Zw2Z4uYjwx+OcVxPCExoflc68HN/nH6klHyimp33PyXzSAJq9GLH6frmxH7WyPK0LZGEpv7gsgCMbrjj2xVe9qpw02lU1Oqbl7Tpy1oUJjRrGRcyZJfCHG2i9bVZasJFkXc6sQwIQlqSJLL5REucJyt/iuK14jhEIobwmf8Q3JakhUNxats4UWQzpDcpfDJbBUHhUlRDBleq75xlWxsTTYpJevc59tk9Dt84fiiCAfGeJbw65BvGnHLGWdGwcUTj8SNBY6ijMeYoiX+AV9yfpn9pgQnLF+8Mx4497o898pdF3Sp5KZFltAkXqz4d5ul3oEhAJ6tmOudYHWy/AX3p2yHNdaOytVTGZDpdMlNoqm+coknaVRX6AQyGJMZJz7FqxlOBqYbc6mNXyi/mrFcnAUlk7eic/e/FZE37pa5Ykb8AQOk22KauJ8hOwzVORNu85gbhAn+RJv6o8U702It1iiUyyvWwPdXLEMurUMIBMCHALRBIACOl99pQKCIGZ1AoNOAQE/HwKAvCASaHA8jHt8l3X3PhDw0Azl+5uPMOyxJuN8KNRSjnRidApdGOe0JUcw3lEFHPvskELXcW6BoPnVEKdzVHhCotdfJNT0boKGqguOlDu5Kw6uHs/P819fct3A2itKYu9JSRwi8SGO04BrhgiMHEOSY9942hhgHrbEliIAOg4gdeGYnUcNoRA3D7iFsSIeqWdXDlqFkmasb/MVFQluzgo+bkjb8NLghHFstLvyGGamng6kUzhuDaRkkhZJMr2MwdTRNfND8V5pUJ/lvINldm/BaKnS5PSCXtN6aCv8mEXmv6S7L1/aL5brl+AbgavuaPYxh+LpkWafJBI+U7xolUX4JF2hD6OtPFNRM/2ijxehL8p5zZhRDW8y+166f84LDEaOqusgGsrhoDUvHQ1zQRfTS8M1ygqHv2jZwPS9wXRCoqRqX9NBwTMc2Lc/0fcNsFPxFwBU8rwPBoCWjuBrlCliq+geTGKOc2T8Mx5hlAhw9/XYUYM/FSphivqQCMYeiPTBcIrrGnM8+6OwfUY0XaCCzHVHdh9Fjv4hqguPtg5LWtWdL+O7DvESwvqFJZSF4mFE+bWl/eO7PlvRK88ywoaTFquJbekwixvCs1q+FGWMIDseZbNBsTVVdsXpLTESsAw2xvSpT2letka37kKWq3mlotAiaWkjS2DSbXMA/KbSdW/9Z7a/Zt6rm4x1Xi46eWn6a7qAqwW5su7Kzeq3xlnx7Nl73LOOV3cjOjNf8NN620sDU7Vk2Q9+4+bXH3DsziLYu5LUHCXpmVvWttKwMJ7wqj+PlqRnXW58eNG1et6/SybpowxlG4CkW0NExdPNAwXRUDp205Vy9LddZyfCIWDi/Uc0QEoqG0+KFv2i5YDU+3mk5KTGsnqoFt62xcXnUOnBUUgUsGaCeB1Vrak+wqo7Jy1j0rISiKwKTbGTWI5N7q0wraOYuF6ZadrNQaDLqrgfm6U2KD3qWI23+PX/LZDRCUx+H0l7bpw43dH5wkfMbXTi/dJsbfOCiO7+vJgj+hb7f5KPVax36vl7cRChFScRdH2RfMHwk//f2Rej35P9yhqxW+3D/m30mcclBVM393cAYKIVpEBwrTS//9E2iwFukBeURX7NxfC40aIxAPy0Yu3lqEnRbQ0vf+jlwp9NvSG6DO3dGo/F8CfLwYfWXHsXj1R/TWA//Aw==&lt;/diagram&gt;&lt;/mxfile&gt;" style="background-color: rgb(255, 255, 255);">
<defs/>
<g>
<rect x="19.5" y="19.5" width="375" height="60" fill="#e6e6e6" stroke="#666666" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 248px; height: 1px; padding-top: 33px; margin-left: 14px;">
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
App
</div>
</div>
</div>
</foreignObject>
<text x="138" y="37" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
App
</text>
</switch>
</g>
<path d="M 214.5 289.5 L 294.95 289.5" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 302.82 289.5 L 292.32 294.75 L 294.95 289.5 L 292.32 284.25 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 187px; margin-left: 168px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
provides
</div>
</div>
</div>
</foreignObject>
<text x="168" y="190" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">
provides
</text>
</switch>
</g>
<rect x="79.5" y="259.5" width="135" height="60" fill="#008a00" stroke="#005700" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 193px; margin-left: 54px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Catalog Plugin
<br/>
<font face="Courier New" style="font-size: 10px;">
plugin:catalog
</font>
</div>
</div>
</div>
</foreignObject>
<text x="98" y="197" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Catalog Plugin...
</text>
</switch>
</g>
<path d="M 214.5 169.5 L 364.5 169.5 Q 379.5 169.5 379.32 184.5 L 378.56 248.15" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 378.47 256.02 L 373.35 245.46 L 378.56 248.15 L 383.84 245.59 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 99px; margin-left: 201px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
<font style="font-size: 8px;">
replaces
<br/>
implementation
<br/>
</font>
</div>
</div>
</div>
</foreignObject>
<text x="201" y="102" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="10px" text-anchor="middle">
replaces...
</text>
</switch>
</g>
<rect x="79.5" y="139.5" width="135" height="60" fill="#e6e6e6" stroke="#666666" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 113px; margin-left: 54px;">
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Extension Overrides
</div>
</div>
</div>
</foreignObject>
<text x="98" y="117" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
Extension Overr...
</text>
</switch>
</g>
<path d="M 214.5 409.5 L 244.5 409.5 Q 259.5 409.5 274.5 409.5 L 294.95 409.5" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 302.82 409.5 L 292.32 414.75 L 294.95 409.5 L 292.32 404.25 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 267px; margin-left: 167px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
provides
</div>
</div>
</div>
</foreignObject>
<text x="167" y="270" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">
provides
</text>
</switch>
</g>
<rect x="79.5" y="379.5" width="135" height="60" fill="#008a00" stroke="#005700" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 273px; margin-left: 54px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Todo Plugin
<br/>
<font face="Courier New" style="font-size: 10px;">
plugin:todo
</font>
</div>
</div>
</div>
</foreignObject>
<text x="98" y="277" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Todo Plugin...
</text>
</switch>
</g>
<rect x="424.5" y="19.5" width="285" height="60" fill="#e6e6e6" stroke="#666666" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 188px; height: 1px; padding-top: 33px; margin-left: 284px;">
<div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Backstage Frontend Framework
</div>
</div>
</div>
</foreignObject>
<text x="378" y="37" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle">
Backstage Frontend Framework
</text>
</switch>
</g>
<path d="M 559.5 289.5 L 553.85 289.59" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 545.97 289.7 L 556.39 284.3 L 553.85 289.59 L 556.55 294.79 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<rect x="304.5" y="259.5" width="255" height="60" fill="#1ba1e2" stroke="#006eaf" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 193px; margin-left: 204px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<font style="font-size: 10px;">
Catalog Client Utility API
<br/>
<font face="Courier New">
api:plugin.catalog.service
</font>
</font>
</div>
</div>
</div>
</foreignObject>
<text x="288" y="197" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Catalog Client Utility API...
</text>
</switch>
</g>
<rect x="304.5" y="379.5" width="255" height="60" fill="#1ba1e2" stroke="#006eaf" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 273px; margin-left: 204px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<font style="font-size: 10px;">
Todo Utility API
<br/>
<font face="Courier New">
api:plugin.todo.api
</font>
</font>
</div>
</div>
</div>
</foreignObject>
<text x="288" y="277" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Todo Utility API...
</text>
</switch>
</g>
<path d="M 49.5 79.5 L 49.5 151.5 Q 49.5 166.5 49.5 181.5 L 49.5 244.5 Q 49.5 259.5 60.11 270.11 L 72.75 282.75" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 78.31 288.31 L 67.18 284.6 L 72.75 282.75 L 74.6 277.18 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 49.5 169.5 L 49.5 241.5 Q 49.5 256.5 49.5 271.5 L 49.5 379.5 Q 49.5 394.5 60.23 399.86 L 70.96 405.23" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 78 408.75 L 66.26 408.75 L 70.96 405.23 L 70.96 399.36 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 49.5 82.5 L 49.5 154.5 Q 49.5 169.5 59.72 169.5 L 69.95 169.5" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 77.82 169.5 L 67.32 174.75 L 69.95 169.5 L 67.32 164.25 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 64px; margin-left: 47px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 10px; font-family: &quot;Courier New&quot;; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
<font face="Helvetica" style="font-size: 8px;">
installs
</font>
</div>
</div>
</div>
</foreignObject>
<text x="47" y="67" fill="rgb(0, 0, 0)" font-family="Courier New" font-size="10px" text-anchor="middle">
installs
</text>
</switch>
</g>
<rect x="469.5" y="139.5" width="195" height="60" fill="#1ba1e2" stroke="#006eaf" stroke-width="1.5" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 113px; margin-left: 314px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<font style="font-size: 10px;">
Fetch Utility API
<br/>
<font face="Courier New">
api:core.fetch
</font>
</font>
</div>
</div>
</div>
</foreignObject>
<text x="378" y="117" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
Fetch Utility API...
</text>
</switch>
</g>
<path d="M 567 79.5 L 567 129.95" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 567 137.82 L 561.75 127.32 L 567 129.95 L 572.25 127.32 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 69px; margin-left: 394px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
provides
</div>
</div>
</div>
</foreignObject>
<text x="394" y="72" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">
provides
</text>
</switch>
</g>
<path d="M 432 379.5 L 432 329.05" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 432 321.18 L 437.25 331.68 L 432 329.05 L 426.75 331.68 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 235px; margin-left: 310px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
depends on
</div>
</div>
</div>
</foreignObject>
<text x="310" y="238" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">
depends on
</text>
</switch>
</g>
<path d="M 559.5 289.5 L 589.5 289.5 Q 604.5 289.5 604.46 274.5 L 604.27 208.75" fill="none" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 604.25 200.88 L 609.53 211.36 L 604.27 208.75 L 599.03 211.39 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-width="1.5" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)scale(1.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 160px; margin-left: 425px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 8px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
depends on
</div>
</div>
</div>
</foreignObject>
<text x="425" y="163" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="8px" text-anchor="middle">
depends on
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

+1 -3
View File
@@ -6,8 +6,6 @@ sidebar_label: Overview
description: The structure and architecture of the new Backend System and its component parts
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
## Building Blocks
This section introduces the high-level building blocks upon which this new
@@ -59,7 +57,7 @@ Just like plugins, modules also have access to services and can depend on their
A detailed explanation of the package architecture can be found in the
[Backstage Architecture
Overview](../../overview/architecture-overview/#package-architecture). The
Overview](../../overview/architecture-overview.md#package-architecture). The
most important packages to consider for this system are the following:
- `plugin-<pluginId>-backend` houses the implementation of the backend plugins
@@ -6,8 +6,6 @@ sidebar_label: Backend
description: Backend instances
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
## The Backend Instance
This is the main entry point for creating a backend. It does not have any functionality in and of itself, but is simply responsible for wiring things together.
@@ -6,8 +6,6 @@ sidebar_label: Services
description: Services for backend plugins
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
Backend services provide shared functionality available to all backend plugins and modules. They are made available through service references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference.
The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created.
@@ -6,8 +6,6 @@ sidebar_label: Plugins
description: Backend plugins
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
Plugins provide the actual base features of a Backstage backend. Each plugin operates completely independently of all other plugins and they only communicate with each other through network calls. This means that there is a strong degree of isolation between plugins, and that each plugin can be considered a separate microservice. While a default Backstage project has all plugins installed within a single backend, it is also possible to split this setup into multiple backends, with each backend housing one or more plugins.
## Defining a Plugin
@@ -6,8 +6,6 @@ sidebar_label: Extension Points
description: Extension points of backend plugins
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
While plugins are able to use static configuration for lightweight forms of customization, you can quickly hit a limit where you need something more powerful to allow users to extend your plugin. For this purpose, the backend system provides a mechanism for plugins to provide extension points, which can be used to expose deeper customizations for your plugin. Extension points are used by modules, which are installed in the backend adjacent to plugins. Modules are covered more in-depth in the [next section](./06-modules.md).
Extension points are quite similar to services, in that they both encapsulate an interface in a reference object. The key difference is that extension points are registered and provided by plugins themselves, and do not have any factory associated with them. Extension points for a given plugin are also only accessible to modules that extend that same plugin.
@@ -6,8 +6,6 @@ sidebar_label: Modules
description: Modules for backend plugins
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
Backend modules are used to extend [plugins](./04-plugins.md) or sometimes other modules with additional features or change existing behavior. They must always be installed in the same backend instance as the plugin that they extend, and may only extend a single plugin. Modules interact with their target plugin using the [extension points](./05-extension-points.md) registered by the plugin, while also being able to depend on the [services](./03-services.md) of that plugin.
Both modules and plugins register an `init` method that is called during startup. In order to ensure that modules have registered all their extensions before the plugin starts up, all modules for each plugin are completely initialized before the plugin itself is initialized. In practice this means that all promises returned by each `init` method of the modules need to resolve before the plugin `init` method is called. This also means that it is not possible to further interact with the extension points once the `init` method has resolved.
@@ -6,8 +6,6 @@ sidebar_label: Naming Patterns
description: Naming patterns in the backend system
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
These are the naming patterns to adhere to within the backend system. They help us keep exports consistent across packages and make it easier to understand the usage and intent of exports.
As a rule, all names should be camel case, with the exceptions of plugin and module IDs, which should be kebab case.
@@ -6,8 +6,6 @@ sidebar_label: Overview
description: Building backends using the new backend system
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
> NOTE: If you have an existing backend that is not yet using the new backend
> system, see [migrating](./08-migrating.md).
@@ -6,8 +6,6 @@ sidebar_label: Migration Guide
description: How to migrate existing backends to the new backend system
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
## Overview
This section describes how to migrate an existing Backstage backend service
@@ -21,8 +19,7 @@ internal plugins and support classes themselves to the backend system first -
the migration here will mostly deal with wiring and using compatibility wrappers
where possible in the backend package itself. We hope that you will find that
you end up with a much smaller, easier to understand, and easier to maintain
package as a result of these steps, and then being able to [migrate
plugins](../building-plugins-and-modules/08-migrating.md) as a separate
package as a result of these steps, and then being able to [migrate plugins](../building-plugins-and-modules/08-migrating.md) as a separate
endeavour later.
## Overall Structure
@@ -262,9 +259,259 @@ If you have other customizations made to `plugins/catalog.ts`, such as adding
custom processors or entity providers, read on. Otherwise, you should be able to
just delete that file at this point.
#### Amazon Web Services
`AwsEksClusterProcessor` and `AwsOrganizationCloudAccountProcessor` have not yet been migrated to the new backend system.
See [Other Catalog Extensions](#other-catalog-extensions) for how to use these in the new backend system.
For `AwsS3DiscoveryProcessor`, first migrate to `AwsS3EntityProvider`.
To migrate `AwsS3EntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-aws` module.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-aws/alpha'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other AWS configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
awsS3:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT1H
timeout: PT50M
/* highlight-add-end */
```
#### Azure DevOps
For `AzureDevOpsDiscoveryProcessor`, first migrate to `AzureDevOpsEntityProvider`.
To migrate `AzureDevOpsEntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-azure` module.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-azure/alpha'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Azure DevOps configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
azureDevOps:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT1H
timeout: PT50M
/* highlight-add-end */
```
#### Open API
`InternalOpenApiDocumentationProvider` has not yet been migrated to the new backend system.
See [Other Catalog Extensions](#other-catalog-extensions) for how to use this in the new backend system.
#### Bitbucket
For `BitbucketDiscoveryProcessor`, migrate to `BitbucketCloudEntityProvider` or `BitbucketServerEntityProvider`
To migrate `BitbucketCloudEntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-bitbucket-cloud` module.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-catalog-backend-module-bitbucket-cloud/alpha'),
);
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Bitbucket Cloud configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
bitbucketCloud:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
```
To migrate `BitbucketServerEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-bitbucket-server`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(
import('@backstage/plugin-catalog-backend-module-bitbucket-server/alpha'),
);
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Bitbucket Server configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
bitbucketServer:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
```
#### Google Cloud Platform
To migrate `GkeEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-gcp`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-gcp'));
/* highlight-add-end */
```
Configuration in app-config.yaml remains the same.
#### Gerrit
To migrate `GerritEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-gerrit`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-gerrit/alpha'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Gerrit configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
gerrit:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
```
#### Github
For `GithubDiscoveryProcessor`, `GithubMultiOrgReaderProcessor` and `GithubOrgReaderProcessor`, first migrate to the equivalent Entity Provider.
To migrate `GithubEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Github configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
github:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
```
To migrate `GithubMultiOrgEntityProvider` and `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
/* highlight-add-start */
backend.add(import('@backstage/plugin-catalog-backend-module-github-org'));
/* highlight-add-end */
```
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Github configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
providers:
githubOrg:
yourProviderId:
# ...
/* highlight-add-start */
schedule:
frequency: PT30M
timeout: PT3M
/* highlight-add-end */
```
If you were providing transformers, these can be configured by extending `githubOrgEntityProviderTransformsExtensionPoint`
```ts title="packages/backend/src/index.ts"
import { createBackendModule } from '@backstage/backend-plugin-api';
import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org';
backend.add(
createBackendModule({
pluginId: 'catalog',
moduleId: 'githubOrgTransformers',
register(env) {
env.registerInit({
deps: {
/* highlight-add-start */
githubOrgTransformers:
githubOrgEntityProviderTransformsExtensionPoint,
/* highlight-add-end */
},
async init({ githubOrgTransformers }) {
/* highlight-add-start */
githubOrgTransformers.setUserTransformer(myUserTransformer);
githubOrgTransformers.setTeamTransformer(myTeamTransformer);
/* highlight-add-end */
},
});
},
}),
);
```
#### Microsoft Graph
Import the Microsoft Graph catalog module
For `MicrosoftGraphOrgReaderProcessor`, first migrate to `MicrosoftGraphOrgEntityProvider`
To migrate `MicrosoftGraphOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-msgraph`.
```ts title="packages/backend/src/index.ts"
backend.add(import('@backstage/plugin-catalog-backend/alpha'));
@@ -273,7 +520,8 @@ backend.add(import('@backstage/plugin-catalog-backend-module-msgraph/alpha'));
/* highlight-add-end */
```
If you were providng a `schedule` programtically, this now needs to be set via configuration
If you were providing a `schedule` in code, this now needs to be set via configuration.
All other Microsoft Graph configuration in `app-config.yaml` remains the same.
```yaml title="app-config.yaml"
catalog:
@@ -285,7 +533,6 @@ catalog:
frequency: PT4H
timeout: PT30M
/* highlight-add-end */
```
If you were providing transformers, these can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`
@@ -6,8 +6,6 @@ sidebar_label: Overview
description: Building backend plugins and modules using the new backend system
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
> NOTE: If you have an existing backend and/or backend plugins that are not yet
> using the new backend system, see [migrating](./08-migrating.md).
@@ -6,8 +6,6 @@ sidebar_label: Testing
description: Learn how to test your backend plugins and modules
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
Utilities for testing backend plugins and modules are available in
`@backstage/backend-test-utils`. This section describes those facilities.
@@ -120,7 +118,7 @@ describe('MyDatabaseClass', () => {
// "physical" databases to test against is much costlier than creating the
// "logical" databases within them that the individual tests use.
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'],
ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'],
});
// Just an example of how to conveniently bundle up the setup code
@@ -6,8 +6,6 @@ sidebar_label: Migration Guide
description: How to migrate existing backend plugins to the new backend system
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
Migrating an existing backend plugin to the new backend system is fairly straightforward. The process is similar across the majority of plugins which just return a `Router` that is then wired up in the `index.ts` file of your backend. The primary thing that we need to do is to make sure that the dependencies that are required by the plugin are available, and then registering the router with the HTTP router service.
Let's look at an example of migrating the Kubernetes backend plugin. In the existing (old) system, the kubernetes backend is structured like this:
@@ -6,8 +6,6 @@ sidebar_label: Overview
description: Core backend service APIs
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, URL Readers, databases and more.
All core services are available through the `coreServices` namespace in the `@backstage/backend-plugin-api` package.
+1 -3
View File
@@ -6,10 +6,8 @@ sidebar_label: Introduction
description: The Backend System
---
> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.**
## Status
The new backend system is in alpha, but many plugins have already been migrated. We recommend all plugins to migrate to the new system, and you can also try it out in your own production deployments.
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
-2
View File
@@ -391,8 +391,6 @@ There are other more specific search results layout components that also accept
## How to migrate your backend installation to use Search together with the new backend system
> DISCLAIMER: The new backend system is in alpha, and so are the search backend support for the new backend system. We don't recommend you to migrate your backend installations to the new system yet. But if you want to experiment, this is the guide for you!
Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up.
In "packages/backend-next/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]:
+178 -1
View File
@@ -35,9 +35,186 @@ process, not the raw originally ingested entity data. See
[The Life of an Entity](./life-of-an-entity.md) for more details about this process and
distinction.
### `GET /entities/by-query`
Query entities. Supports the following query parameters, described in the section below:
- [`filter`](#filtering), for selecting only a subset of all entities
- [`fields`](#field-selection), for selecting only parts of the full data
structure of each entity
- `limit` for limiting the number of entities returned (20 is the default)
- [`orderField`](#ordering), for deciding the order of the entities
- [`fullTextFilter`](#full-text-filtering), for filtering the entities by text
- [`cursor`](#pagination), for retrieving the next or previous batch of entities
The return type is JSON, on the following form
```json
{
"items": [{ "kind": "Component", "metadata": { "name": "foo" } }],
"totalItems": 4,
"pageInfo": {
"nextCursor": "a-cursor",
"prevCursor": "another-cursor"
}
}
```
#### Filtering
You can pass in one or more filter sets that get matched against each entity.
Each filter set is a number of conditions that all have to match for the
condition to be true (conditions effectively have an AND between them). At least
one filter set has to be true for the entity to be part of the result set
(filter sets effectively have an OR between them).
Example:
```text
/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type
Return entities that match
Filter set 1:
Condition 1: kind = user
AND
Condition 2: metadata.namespace = default
OR
Filter set 2:
Condition 1: kind = group
AND
Condition 2: spec.type exists
```
Each condition is either on the form `<key>`, or on the form `<key>=<value>`.
The first form asserts on the existence of a certain key (with any value), and
the second asserts that the key exists and has a certain value. All checks are
always case _insensitive_.
In all cases, the key is a simplified JSON path in a given piece of entity data.
Each part of the path is a key of an object, and the traversal also descends
through arrays. There are two special forms:
- Array items that are simple value types (such as strings) match on a key-value
pair where the key is the item as a string, and the value is the string `true`
- Relations can be matched on a `relations.<type>=<targetRef>` form
Let's look at a simplified example to illustrate the concept:
```json
{
"a": {
"b": ["c", { "d": 1 }],
"e": 7
}
}
```
This would match any one of the following conditions:
- `a`
- `a.b`
- `a.b.c`
- `a.b.c=true`
- `a.b.d`
- `a.b.d=1`
- `a.e`
- `a.e=7`
Some more real world usable examples:
- Return all orphaned entities:
`/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true`
- Return all users and groups:
`/entities/by-query?filter=kind=user&filter=kind=group`
- Return all service components:
`/entities/by-query?filter=kind=component,spec.type=service`
- Return all entities with the `java` tag:
`/entities/by-query?filter=metadata.tags.java`
- Return all users who are members of the `ops` group (note that the full
[reference](references.md) of the group is used):
`/entities/by-query?filter=kind=user,relations.memberof=group:default/ops`
#### Full text filtering
TODO
#### Field selection
By default the full entities are returned, but you can pass in a `fields` query
parameter which selects what parts of the entity data to retain. This makes the
response smaller and faster to transfer, and may allow the catalog to perform
more efficient queries.
The query parameter value is a comma separated list of simplified JSON paths
like above. Each path corresponds to the key of either a value, or of a subtree
root that you want to keep in the output. The rest is pruned away. For example,
specifying `?fields=metadata.name,metadata.annotations,spec` retains only the
`name` and `annotations` fields of the `metadata` of each entity (it'll be an
object with at most two keys), keeps the entire `spec` unchanged, and cuts out
all other roots such as `relations`.
Some more real world usable examples:
- Return only enough data to form the full ref of each entity:
`/entities/by-query?fields=kind,metadata.namespace,metadata.name`
#### Ordering
By default the entities are returned ordered by their internal uid. You can
customize the `orderField` query parameters to affect that ordering.
For example, to return entities by their name:
`/entities/by-query?orderField=metadata.name,asc`
Each parameter can be followed by `asc` for ascending lexicographical order or
`desc` for descending (reverse) lexicographical order.
#### Pagination
You may pass the `cursor` query parameters to perform cursor based pagination
through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property:
```json
"pageInfo": {
"nextCursor": "a-cursor",
"prevCursor": "another-cursor"
}
```
If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach,
if `prevCursor` exists, it can be used to retrieve the previous batch of entities.
- [`filter`](#filtering), for selecting only a subset of all entities
- [`fields`](#field-selection), for selecting only parts of the full data
structure of each entity
- `limit` for limiting the number of entities returned (20 is the default)
- [`orderField`](#ordering), for deciding the order of the entities
- `fullTextFilter`
**NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that,
it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters,
as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration.
### `GET /entities`
Lists entities. Supports the following query parameters, described in sections
Lists entities.
**NOTE**: This endpoint is deprecated in favor of `GET /entities/by-query`, which provides a more efficient implementation and cursor based pagination.
The endpoint supports the following query parameters, described in sections
below:
- [`filter`](#filtering), for selecting only a subset of all entities
@@ -231,8 +231,53 @@ spec:
inputType: tel
```
### Using Secrets
You may want to mark things as secret and make sure that these values are protected and not available through REST endpoints. You can do this by using the built in `ui:field: Secret`.
You can define this property as any normal parameter, however the consumption of this parameter will not be available through `${{ parameters.myKey }}` you will instead need to use `${{ secrets.myKey }}` in your `template.yaml`.
Parameters will be automatically masked in the review step.
```yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: v1beta3-demo
title: Test Action template
description: scaffolder v1beta3 template demo
spec:
owner: backstage/techdocs-core
type: service
parameters:
- title: Authenticaion
description: Provide authentication for the resource
required:
- username
- password
properties:
username:
type: string
# use the built in Secret field extension
ui:field: Secret
password:
type: string
ui:field: Secret
steps:
- id: setupAuthentication
action: auth:create
input:
# make sure to use ${{ secrets.parameterName }} to reference these values
username: ${{ secrets.username }}
password: ${{ secrets.password }}
```
### Hide or mask sensitive data on Review step
> Note: this approach is soon to be deprecated, please mark things as secret by using the `Secret` field extension instead as mentioned above.
Sometimes, specially in custom fields, you collect some data on Create form that
must not be shown to the user on Review step. To hide or mask this data, you can
use `ui:widget: password` or set some properties of `ui:backstage`:
@@ -6,7 +6,7 @@ sidebar_label: Overview
description: The structure and architecture of the new Frontend System
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
## Building Blocks
+34 -3
View File
@@ -1,12 +1,12 @@
---
id: apps
id: app
title: App Instances
sidebar_label: App
# prettier-ignore
description: App instances
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
## The App Instance
@@ -45,4 +45,35 @@ A common type of data that is shared between extensions is React elements and co
## Feature Discovery
TODO
App feature discovery lets you automatically discover and install features provided by dependencies in your app. In practice, it means that you don't need to manually `import` features in code, but they are instead installed as soon as you add them as a dependency in your `package.json`.
Because feature discovery needs to interact with the compilation process, it is only available when using the `@backstage/cli` to build your app. It is hooked into the WebPack compilation process by scanning your app package for compatible dependencies, which are then made part of the app compilation bundle.
Since the `@backstage/cli` is a more stable component than the new frontend system, feature discovery is currently marked as an experimental feature of the CLI and needs to be enabled manually. To enable it, add the following configuration to your `app-config.yaml`:
```yaml
app:
experimental:
packages: all
```
This will cause all dependencies in your app package to be installed automatically. If this is not desired, you can use include or exclude filters to narrow down the set of packages:
```yaml
app:
experimental:
packages:
# Only the following packages will be included
include:
- '@backstage/plugin-catalog'
- '@backstage/plugin-scaffolder'
---
app:
experimental:
packages:
# All but the following package will be included
exclude:
- '@backstage/plugin-catalog'
```
Note that you do not need to manually exclude packages that you also import explicitly in code, since plugin instances are deduplicated by the app. You will never end up with duplicate plugin installations except if they are in fact two different plugin instances with different IDs.
@@ -6,7 +6,7 @@ sidebar_label: Extensions
description: Frontend extensions
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
As mentioned in the [previous section](./02-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns.
@@ -6,7 +6,7 @@ sidebar_label: Plugins
description: Frontend plugins
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
## Introduction
@@ -1,12 +1,12 @@
---
id: extension overrides
id: extension-overrides
title: Frontend Extension Overrides
sidebar_label: Extension Overrides
# prettier-ignore
description: Frontend extension overrides
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
## Introduction
@@ -6,6 +6,25 @@ sidebar_label: Utility APIs
description: Utility APIs
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
See [Utility APIs docs](../../api/utility-apis.md).
## Overview
Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config.
A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through.
The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also chooses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them.
![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg)
## Extension structure
All utility APIs implement the `createApiExtension.factoryDataRef` output data type, and must attach exclusively to the `core` extension's `apis` input no matter who provided them. These defaults are provided out of the box by the `createApiExtension` framework function.
Since utility APIs are extensions, they can also have inputs in advanced use cases. This is occasionally useful for complex APIs that can themselves be extended with additional programmatic functionality by adopters.
## Links
- The [Utility APIs section](../utility-apis/01-index.md) of the plugin docs
- The legacy docs on [utility APIs](../../api/utility-apis.md)
@@ -6,7 +6,7 @@ sidebar_label: Routes
description: Frontend routes
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
See [routing system docs](../../plugins/composability.md#routing-system)
@@ -98,7 +98,7 @@ Explain all of create, use and provide
-->
```ts
/*
/*
Some examples
@@ -6,7 +6,7 @@ sidebar_label: Naming Patterns
description: Naming patterns in the frontend system
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
These are the naming patterns to adhere to within the frontend system. They help us keep exports and IDs consistent across packages and make it easier to understand the usage and intent of exports and IDs.
@@ -6,7 +6,7 @@ sidebar_label: Value References
description: Value References
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
<!--
@@ -0,0 +1,235 @@
---
id: testing
title: Frontend System Testing Plugins
sidebar_label: Testing
# prettier-ignore
description: Testing plugins in the frontend system
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
# Testing Frontend Plugins
> NOTE: The new frontend system is in alpha, and some plugins do not yet fully implement it.
Utilities for testing frontend features and components are available in `@backstage/frontend-test-utils`.
## Testing React components
A component can be used for more than one extension, and it should be tested independently of an extension environment.
Use the `renderInTestApp` helper to render a given component inside a Backstage test app:
```tsx
import React from 'react';
import { screen } from '@testing-library/react';
import { renderInTestApp } from '@backstage/frontend-test-utils';
import { EntityDetails } from './plugin';
describe('Entity details component', () => {
it('should render the entity name and owner', async () => {
await renderInTestApp(<EntityDetails owner="tools" name="test" />);
await expect(
screen.findByText('The entity "test" is owned by "tools"'),
).resolves.toBeInTheDocument();
});
});
```
To mock [Utility APIs](../architecture/06-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API:
```tsx
import React from 'react';
import { screen } from '@testing-library/react';
import {
renderInTestApp,
TestApiProvider,
} from '@backstage/frontend-test-utils';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { EntityDetails } from './plugin';
describe('Entity details component', () => {
it('should render the entity name and owner', async () => {
const catalogApiMock = {
async getEntityFacets() {
return {
facets: {
'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }],
},
},
}
} satisfies Partial<typeof catalogApiRef.T>;
const entityRef = stringifyEntityRef({
kind: 'Component',
namespace: 'default',
name: 'test',
});
await renderInTestApp(
<TestApiProvider apis={[[catalogApiRef, catalogApiMock]]}>
<EntityDetails entitRef={entityRef} />
</TestApiProvider>,
);
await expect(
screen.findByText('The entity "test" is owned by "tools"'),
).resolves.toBeInTheDocument();
});
});
```
## Testing extensions
To facilitate testing of frontend extensions, the `@backstage/frontend-test-utils` package provides a tester class which starts up an entire frontend harness, complete with a number of default features. You can then provide overrides for extensions whose behavior you need to adjust for the test run.
A number of features (frontend extensions and overrides) are also accepted by the tester. Here are some examples of how these facilities can be useful:
### Single extension
In order to test an extension in isolation, you simply need to pass it into the tester factory, then call the render method on the returned instance:
```tsx
import { screen } from '@testing-library/react';
import { createExtensionTester } from '@backstage/frontend-test-utils';
import { indexPageExtension } from './plugin';
describe('Index page', () => {
it('should render a the index page', () => {
createExtensionTester(indexPageExtension).render();
expect(screen.getByText('Index Page')).toBeInTheDocument();
});
});
```
### Extension preset
There are some extensions that rely on other extensions existence, such as a page that links to another page. In that case, you can add more than one extension to the preset of features you want to render in the test, as shown below:
```tsx
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createExtensionTester } from '@backstage/frontend-test-utils';
import { indexPageExtension, detailsPageExtension } from './plugin';
describe('Index page', async () => {
it('should link to the details page', () => {
createExtensionTester(indexPageExtension)
// Adding more extensions to the preset being tested
.add(detailsPageExtension)
.render();
await expect(screen.findByText('Index Page')).toBeInTheDocument();
await userEvent.click(screen.getByRole('link', { name: 'See details' }));
await expect(
screen.findByText('Details Page'),
).resolves.toBeInTheDocument();
});
});
```
### Mocking apis
If your extensions requires implementation of APIs that aren't wired up by default, you'll have to add overrides to the preset of features being tested:
```tsx
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createApiFactory } from '@backestage/core-plugin-api';
import {
createExtensionOverrides,
configApiRef,
analyticsApiRef,
} from '@backstage/frontend-plugin-api';
import {
createExtensionTester,
MockConfigApi,
MockAnalyticsApi,
} from '@backstage/frontend-test-utils';
import { indexPageExtension } from './plugin';
describe('Index page', () => {
it('should capture click events in analytics', async () => {
// Mocking the analytics api implementation
const analyticsApiMock = new MockAnalyticsApi();
const analyticsApiOverride = createApiExtension({
factory: createApiFactory({
api: analyticsApiRef,
factory: () => analyticsApiMock,
}),
});
createExtensionTester(indexPageExtension)
// Overriding the analytics api extension
.add(analyticsApiOverride)
.render();
await userEvent.click(
await screen.findByRole('link', { name: 'See details' }),
);
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
action: 'click',
subject: 'See details',
});
});
});
```
### Setting configuration
In the case that your extension can be configured, you can test this capability by passing configuration values as follows:
```tsx
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { createExtensionTester } from '@backstage/frontend-test-utils';
import { indexPageExtension, detailsPageExtension } from './plugin';
describe('Index page', () => {
it('should accepts a custom title via config', async () => {
createExtensionTester(indexPageExtension, {
// Configuration specific of index page
config: { title: 'Custom index' },
})
.add(detailsExtensionPage, {
// Configuration specific of details page
config: { title: 'Custom details' },
})
.render({
// Configuration specific of the instance
config: {
app: {
title: 'Custom app',
},
},
});
await expect(
screen.findByRole('heading', { name: 'Custom app' }),
).resolves.toBeInTheDocument();
await expect(
screen.findByRole('heading', { name: 'Custom index' }),
).resolves.toBeInTheDocument();
await userEvent.click(screen.getByRole('link', { name: 'See details' }));
await expect(
screen.findByText('Custom details'),
).resolves.toBeInTheDocument();
});
});
```
That's all for testing features!
## Missing something?
If there's anything else you think needs to be covered in the docs or that you think isn't covered by the test utilities, please create an issue in the Backstage repository. You are always welcome to contribute as well!
+2 -2
View File
@@ -6,10 +6,10 @@ sidebar_label: Introduction
description: The Frontend System
---
> **NOTE: The new frontend system is in a highly experimental phase**
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
## Status
The new frontend system is in an experimental phase and we do not recommend any plugins or apps to migrate.
The new frontend system is in alpha, and only a few plugins support the system so far. We do not yet recommend migrating any apps to the new system. If you add support for the new system to your plugin, please do so under a `/alpha` sub-path export.
You can find an example app setup in [the `app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next).
@@ -0,0 +1,43 @@
---
id: index
title: Utility APIs
sidebar_label: Overview
# prettier-ignore
description: Working with Utility APIs in the New Frontend System
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract.
## Creating utility APIs
> For details, [see the main article](./02-creating.md).
Backstage apps, plugins, and the core Backstage framework can all expose utility APIs for general use.
Some are available out of the box, such as the API for reading app configuration. Some are provided by third party plugins, such as the catalog client API that both the catalog itself and your own code can leverage to talk to the catalog backend. Some, you may create yourself and make available inside your Backstage instance for use within your private ecosystem of plugins.
[The main article](./02-creating.md) describes the process of creating and exposing utility APIs of your own, for sharing functionality or configurability across plugins and apps.
## Consuming utility APIs
> For details, [see the main article](./03-consuming.md).
Once utility APIs are created, there are a few ways that they can be accessed to be consumed.
Some utility APIs in turn depend on other utility APIs. This powerful composability lets you leverage already-written reusable pieces. In particular, you may want to rely on Backstage's framework-provided APIs e.g. for reading app configuration and many other use cases. Sometimes you request utility APIs inside your React components, e.g. for accessing i18n strings, or emitting analytics events.
These are described in detail in [the main article](./03-consuming.md)
## Configuring utility APIs
> For details, [see the main article](./04-configuring.md).
Most utility APIs are usable directly without any configuration. But they are proper extensions, and can therefore have their implementations entirely swapped out by your app for advanced use cases. They can also be built with the ability to configured in your app-config, or to have inputs that extend their functionality.
These cases are all described in [the main article](./04-configuring.md).
## Migrating from the old frontend system
If you want to learn how to migrate your own utility APIs from the old frontend system to the new one, that's described in [a dedicated migration guide](./05-migrating.md).
@@ -0,0 +1,145 @@
---
id: creating
title: Creating Utility APIs
sidebar_label: Creating APIs
# prettier-ignore
description: Creating new utility APIs in your plugins and app
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
This section describes how to make a Utility API from scratch, or to add configurability and inputs to an existing one. If you are instead interested in migrating an existing Utility API from the old frontend system, check out [the migrating section](./05-migrating.md).
## Creating the Utility API contract
The first step toward exposing a utility API is to define its TypeScript contract, as well as an API reference for consumers use to access the implementation. If you want your API to be accessible by other plugins this should be done in [your plugin's `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md), so that it can be imported separately. If you just want to use the API within your own plugin it is fine to place the definition within the plugin itself. In this example, we have an Example plugin that wants to expose a utility API for performing some type of work.
```tsx title="in @internal/plugin-example-react"
import { createApiRef } from '@backstage/frontend-plugin-api';
/**
* The work interface for the Example plugin.
* @public
*/
export interface WorkApi {
/**
* Performs some work.
*/
doWork(): Promise<void>;
}
/**
* API Reference for {@link WorkApi}.
* @public
*/
export const workApiRef = createApiRef<WorkApi>({
id: 'plugin.example.work',
});
```
Both of these are properly exported publicly from the package, so that consumers can reach them.
## Providing an extension through your plugin
The plugin itself now wants to provide this API and its default implementation, in the form of an API extension. Doing so means that when users install the Example plugin, an instance of the Work utility API will also be automatically available in their apps - both to the Example plugin itself, and to others. We do this in the main plugin package, not the `-react` package.
```tsx title="in @internal/plugin-example"
import {
createApiExtension,
createApiFactory,
createPlugin,
storageApiRef,
StorageApi,
} from '@backstage/frontend-plugin-api';
import { WorkApi, workApiRef } from '@internal/plugin-example-react';
class WorkImpl implements WorkApi {
constructor(options: { storageApiRef: StorageApi }) {
/* TODO */
}
async doWork() {
/* TODO */
}
}
const exampleWorkApi = createApiExtension({
factory: createApiFactory({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => {
return new WorkImpl({ storageApi });
},
}),
});
/**
* The Example plugin.
* @public
*/
export default createPlugin({
id: 'example',
extensions: [exampleWorkApi],
});
```
For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. Users who install the plugin will now get the utility API automatically as well.
The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function.
The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/08-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere.
## Adding configurability
Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's make the required additions to our original work example API.
```tsx title="in @internal/plugin-example"
/* highlight-add-next-line */
import { createSchemaFromZod } from '@backstage/frontend-plugin-api';
const exampleWorkApi = createApiExtension({
/* highlight-add-start */
api: workApiRef,
configSchema: createSchemaFromZod(z =>
z.object({
goSlow: z.boolean().default(false),
}),
),
/* highlight-add-end */
/* highlight-remove-next-line */
factory: createApiFactory({
/* highlight-add-next-line */
factory: ({ config }) => createApiFactory({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => {
/* highlight-add-start */
if (config.goSlow) {
/* ... */
}
/* highlight-add-end */
},
}),
});
```
We wanted users to be able to set a `goSlow` extension config parameter for our API instances. So we passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual extension config values will then be passed in a type safe manner in to the `factory` which is now a callback, wherein we can do what we wish with them. When changing to the callback form, we also had to add a top level `api: workApiRef` under `createApiExtension`.
Note that the expression "extension config" as used here, is _not_ the same thing as the `configApi` which gives you access to the full app-config. The extension config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the Configuring section](./04-configuring.md).
Note also that the extension config schema contained a default value fo the `goSlow` field. This is an important consideration. You want users of your API to be able to get maximum value out of it, without having to dive deep into how to configure it. For that reason you generally want to provide as many sane defaults as possible, while letting users override them rarely but with purpose, only when called for. If you have an extension config schema without defaults, the framework will refuse to instantiate the utility API on startup unless the user had configured those values explicitly. Since it had a default value, the TypeScript code and interfaces also don't have to defensively allow `undefined` - we know that it'll have either the default value or an overridden value when we start consuming the extension config data.
## Adding inputs
Inputs are added to Utility APIs in the same way as other extension types:
- Declaring a set of `inputs` on your extension
- If needed, create custom extension data types to be used in those inputs
- If needed, export an extension creator function for creating that particular attachment type
This is a power use case and not very commonly used.
<!-- TODO: link to main article -->
## Next steps
See [the Consuming section](./03-consuming.md) to see how to consume this new utility API in various ways. If you wish to configure and add inputs to it, check out [the Configuring section](./04-configuring.md).
@@ -0,0 +1,69 @@
---
id: consuming
title: Consuming Utility APIs
sidebar_label: Consuming APIs
# prettier-ignore
description: Consuming utility APIs
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
All of the utility API extensions that were passed into your app through installed plugins, get instantiated and configured in the right order by the framework, and are then made available for consumption. You can interact with these instances in the following ways.
## Via React hooks
The most common consumption pattern for utility APIs is to call the `useApi` hook inside React components to get an implementation via its API ref. This applies whether it was originally provided from the core framework or from a plugin.
```tsx
import { useApi, configApiRef } from '@backstage/frontend-plugin-api';
const MyComponent = () => {
const configApi = useApi(configApiRef);
const title = configApi.getString('app.title');
// ...
};
```
The `useApi` hook always returns a value, or throws an exception if the API ref could not be resolved to a registered implementation. For advanced use cases, where you explicitly want to optionally request a utility API that may or may not have been provided at runtime, you can use the underlying `useApiHolder` hook instead.
```tsx
import { useApiHolder, configApiRef } from '@backstage/frontend-plugin-api';
const MyComponent = () => {
const apis = useApiHolder();
const configApi = apis.get(configApiRef); // may return undefined
if (configApi) {
const title = configApi.getString('app.title');
// ...
}
};
```
## Via dependencies
Your utility APIs can depend on other utility APIs in their factories. You do this by declaring `deps` on your `createApiFactory`, and reading the outcome in your `factory`.
```tsx
import {
configApiRef,
createApiExtension,
createApiFactory,
discoveryApiRef,
} from '@backstage/frontend-plugin-api';
import { MyApiImpl } from './MyApiImpl';
const myApi = createApiExtension({
factory: createApiFactory({
api: myApiRef,
deps: {
configApi: configApiRef,
discoveryApi: discoveryApiRef,
},
factory: ({ configApi, discoveryApi }) => {
return new MyApiImpl({ configApi, discoveryApi });
},
}),
});
```
Note how the `deps` section essentially assigns free-form names that you choose, to API refs. Here we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref.
@@ -0,0 +1,76 @@
---
id: configuring
title: Configuring Utility APIs
sidebar_label: Configuring
# prettier-ignore
description: Configuring, extending, and overriding utility APIs
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
Utility APIs are extensions and can therefore optionally be amended with configurability, as well as inputs that other extensions attach themselves to. This section describes how to make use of that as a consumer of such utility APIs.
## Configuring
To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/08-naming-patterns.md) for details.
Our example work API from [the creating section](./02-creating.md) would have the ID `api:plugin.example.work`. You configure it and all other extensions under the `app.extensions` section of your app-config.
```yaml title="in e.g. app-config.yaml or app-config.production.yaml"
app:
extensions:
- api:plugin.example.work:
config:
goSlow: false
- # ... other extensions
```
It's important to note that the `extensions` are a list (mind the initial `-`), and that the `api:plugin.example.work` entry is an object such that the `config` key needs to be indented below it. If you do not get those two pieces right, the application may not start up correctly.
The extension config schema will tell you what parameters it supports. Here we override the `goSlow` extension config value, which replaces the default.
## Attaching extensions to inputs
Like with other extension types, you add input attachments to a Utility API by declaring the `attachTo` section of that attachment to point to the Utility APIs ID and input name.
Well written input-enabled extension often have extension creator functions that help you make such attachments. Those functions typically set the `attachTo` section correctly on your behalf so that you don't have to figure them out.
## Replacing a Utility API implementation
Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/05-extension-overrides.md).
```tsx title="in your app"
/* highlight-add-start */
import { createExtensionOverrides } from '@backstage/frontend-plugin-api';
class CustomWorkImpl implements WorkApi {
/* ... */
}
const myOverrides = createExtensionOverrides({
extensions: [
createApiExtension({
api: workApiRef,
factory: () =>
createApiFactory({
api: workApiRef,
factory: () => new CustomWorkImpl(),
}),
}),
],
});
/* highlight-add-end */
// Remember to pass the overrides to your createApp
export default createApp({
features: [
// ... other features
/* highlight-add-next-line */
myOverrides,
],
});
```
In this example the overriding extension is kept minimal, but just like any other extension it can also have `deps`, configurability, and inputs. Check out [the Creating section](./02-creating.md) for more details about that.
When you create a replacement extension, in general you may want to mimic its extension config schema or input shapes where applicable. This makes it an easier thing to slot in to an app, since it'll be responding to extensibility the same way as the original one did.
@@ -0,0 +1,126 @@
---
id: migrating
title: Migrating Utility APIs from the old frontend system
sidebar_label: Migrating
# prettier-ignore
description: Migrating Utility APIs from the old frontend system
---
> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.**
If you are migrating your plugins or app over from the old frontend system, there are a few things to keep in mind in regards to utility APIs.
## Overview
- Migrate your repo overall to the latest release of Backstage
- Follow the plugin migration guide <!-- TODO: Link -->
- Optionally change your package dependencies and code from `core-*-api` to `frontend-*-api`
- Keep the TypeScript interface and API ref exported as they were, except possibly reconsidering the choice of ID of the latter
- Wrap the old API factory call in an extension using `createApiExtension`
- Make sure that this extension is referenced by your migrated plugin
## Prerequisites
This guide assumes that you first [upgrade your repo](../../getting-started/keeping-backstage-updated.md) to the latest release of Backstage. This ensures that you do not have to fight several types of incompatibilities and updates at the same time.
## Dependency changes
In this article we will discuss some old interfaces that you used to import from the `@backstage/core-plugin-api` package. Those are now generally lifted over to `@backstage/frontend-plugin-api`, next to the new interfaces that are specific to the new frontend system. If you want to, you can already update your `package.json` and code imports to only use the new plugin API, but for the time being you don't have to. The old core exports will continue to work for the foreseeable future.
To at least get access to the new interfaces, you'll need to run the following command. Note that it's just an example! It refers to `plugins/example`, which you'll have to change to the actual folder name that your package to migrate is in.
```bash title="from your repo root"
yarn --cwd plugins/example add @backstage/frontend-plugin-api
```
## React package interface and ref changes
Let's begin with [your `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md). The act of exporting TypeScript interfaces and API refs have not changed from the old system. You can typically keep those as-is. For illustrative purposes, this is an example of an interface and its API ref:
```tsx title="in @internal/plugin-example-react"
import { createApiRef } from '@backstage/frontend-plugin-api';
/**
* Performs some work.
* @public
*/
export interface WorkApi {
doWork(): Promise<void>;
}
/**
* The work interface for the Example plugin.
* @public
*/
export const workApiRef = createApiRef<WorkApi>({
id: 'plugin.example.work',
});
```
In this example, the plugin ID already follows <!-- TODO: Link --> the common naming convention. If it doesn't, you may want to consider renaming that ID at this point. Don't worry, this won't hurt consumers in the old frontend system since the ID is mostly used for debugging purposes there. In the new system, it's much more important and appears in app-config files and similar.
Note at the top of the file that it uses the updated import from `@backstage/frontend-plugin-api` that we migrated in the previous section, instead of the old `@backstage/core-plugin-api`.
## Plugin package changes
Now let's turn to the main plugin package where the plugin itself is exported. You will probably already have a `createPlugin` call in here. Before we changed the `core-plugin-api` imports it'll have looked somewhat similar to the following:
```tsx title="in @internal/plugin-example, NOTE THIS IS LEGACY CODE"
import {
storageApiRef,
createPlugin,
createApiFactory,
} from '@backstage/core-plugin-api';
import { workApiRef } from '@internal/plugin-example-react';
import { WorkImpl } from './WorkImpl';
const exampleWorkApi = createApiFactory({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => new WorkImpl({ storageApi }),
});
/** @public */
export const catalogPlugin = createPlugin({
id: 'example',
apis: [exampleWorkApi],
});
```
The major changes we'll make are
- Optionally change the old imports to the new package as per the top section of this guide
- Wrap the existing API factory in a `createApiExtension`
- Change to the new version of `createPlugin` which exports this extension
- Change the plugin export to be the default instead
The end result, after simplifying imports and cleaning up a bit, might look like this:
```tsx title="in @internal/plugin-example"
import {
storageApiRef,
createPlugin,
createApiFactory,
createApiExtension,
} from '@backstage/frontend-plugin-api';
import { workApiRef } from '@internal/plugin-example-react';
import { WorkImpl } from './WorkImpl';
const exampleWorkApi = createApiExtension({
factory: createApiFactory({
api: workApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => new WorkImpl({ storageApi }),
}),
});
/** @public */
export default createPlugin({
id: 'example',
extensions: [exampleWorkApi],
});
```
## Further work
Since utility APIs are now complete extensions, you may want to take a bigger look at how they used to be used, and what the new frontend system offers. You may for example consider [adding configurability or inputs](./02-creating.md) to your API, if that makes sense for your current application.
+3 -3
View File
@@ -134,7 +134,7 @@ links:
There are multiple authentication providers available for you to use with
Backstage, feel free to follow
[the instructions for adding authentication](../auth/).
[the instructions for adding authentication](../auth/index.md).
For this tutorial we choose to use GitHub, a free service most of you might be
familiar with. For other options, see
@@ -205,7 +205,7 @@ Restart Backstage from the terminal, by stopping it with `Control-C`, and starti
To learn more about Authentication in Backstage, here are some docs you
could read:
- [Authentication in Backstage](../auth/)
- [Authentication in Backstage](../auth/index.md)
- [Using organizational data from GitHub](../integrations/github/org.md)
### Setting up a GitHub Integration
@@ -254,7 +254,7 @@ integrations:
Some helpful links, for if you want to learn more about:
- [Other available integrations](../integrations/)
- [Other available integrations](../integrations/index.md)
- [Using GitHub Apps instead of a Personal Access Token](../integrations/github/github-apps.md#docsNav)
### Explore what we've done so far
+2 -8
View File
@@ -32,7 +32,7 @@ catalog:
bucketName: sample-bucket
prefix: prefix/ # optional
region: us-east-2 # optional, uses the default region otherwise
schedule: # optional; same options as in TaskScheduleDefinition
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
@@ -52,7 +52,7 @@ catalog:
bucketName: sample-bucket
prefix: prefix/ # optional
region: us-east-2 # optional, uses the default region otherwise
schedule: # optional; same options as in TaskScheduleDefinition
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
@@ -79,12 +79,6 @@ const builder = await CatalogBuilder.create(env);
builder.addEntityProvider(
AwsS3EntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
+1 -1
View File
@@ -71,7 +71,7 @@ The parameters available are:
- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched.
- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml.
- **`branch:`** _(optional)_ The branch name to use.
- **`schedule`** _(optional)_:
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
+2 -15
View File
@@ -49,19 +49,6 @@ export default async function createPlugin(
}
```
Alternatively to the config-based schedule, you can use
```ts
/* highlight-remove-next-line */
scheduler: env.scheduler,
/* highlight-add-start */
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
/* highlight-add-end */
```
### Installation with Events Support
Please follow the installation instructions at
@@ -131,7 +118,7 @@ catalog:
filters: # optional
projectKey: '^apis-.*$' # optional; RegExp
repoSlug: '^service-.*$' # optional; RegExp
schedule: # optional; same options as in TaskScheduleDefinition
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
@@ -153,7 +140,7 @@ catalog:
Regular expression used to filter results based on the project key.
- **`repoSlug`** _(optional)_:
Regular expression used to filter results based on the repo slug.
- **`schedule`** _(optional)_:
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
@@ -38,12 +38,6 @@ export default async function createPlugin(
builder.addEntityProvider(
BitbucketServerEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
@@ -69,7 +63,7 @@ catalog:
filters: # optional
projectKey: '^apis-.*$' # optional; RegExp
repoSlug: '^service-.*$' # optional; RegExp
schedule: # optional; same options as in TaskScheduleDefinition
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
@@ -87,7 +81,7 @@ catalog:
Regular expression used to filter results based on the project key.
- **`repoSlug`** _(optional)_:
Regular expression used to filter results based on the repo slug.
- **`schedule`** _(optional)_:
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
+1 -8
View File
@@ -26,18 +26,11 @@ Then add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.
```ts
/* packages/backend/src/plugins/catalog.ts */
import { GerritEntityProvider } from '@backstage/plugin-catalog-backend-module-gerrit';
import { Duration } from 'luxon';
const builder = await CatalogBuilder.create(env);
/** ... other processors and/or providers ... */
builder.addEntityProvider(
GerritEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
@@ -57,7 +50,7 @@ catalog:
host: gerrit-your-company.com
branch: master # Optional
query: 'state=ACTIVE&prefix=webapps'
schedule: # optional; same options as in TaskScheduleDefinition
schedule:
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
+7 -20
View File
@@ -40,12 +40,6 @@ export default async function createPlugin(
builder.addEntityProvider(
GithubEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
}),
);
@@ -85,12 +79,6 @@ export default async function createPlugin(
/* highlight-add-start */
const githubProvider = GithubEntityProvider.fromConfig(env.config, {
logger: env.logger,
// optional: alternatively, use scheduler with schedule defined in app-config.yaml
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 30 },
timeout: { minutes: 3 },
}),
// optional: alternatively, use schedule
scheduler: env.scheduler,
});
env.eventBroker.subscribe(githubProvider);
@@ -122,7 +110,7 @@ catalog:
filters:
branch: 'main' # string
repository: '.*' # Regex
schedule: # optional; same options as in TaskScheduleDefinition
schedule: # same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
@@ -213,7 +201,7 @@ This provider supports multiple organizations via unique provider IDs.
Defaults to `false`.
Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in
conjunction with wildcards in the `catalogPath`.
- **`schedule`** _(optional)_:
- **`schedule`**:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
@@ -229,13 +217,12 @@ GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-r
accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location.
If your requests are too frequent then you may get throttled by
rate limiting. You can change the refresh frequency of the catalog in your `packages/backend/src/plugins/catalog.ts` file:
rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`.
```typescript
schedule: env.scheduler.createScheduledTaskRunner({
frequency: { minutes: 35 },
timeout: { minutes: 30 },
}),
```yaml
schedule:
frequency: { minutes: 35 }
timeout: { minutes: 3 }
```
More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page.
+8
View File
@@ -182,3 +182,11 @@ The TypeScript release cadence is roughly every three months. An important aspec
Our policy is to support the last 3 TypeScript versions, for example 4.8, 4.9, and 5.0. Converted to time, this means that we typically support the TypeScript version from the last six to nine months, depending on where in the TypeScript release window we are. This policy applies as a snapshot at the time of any given Backstage release, new TypeScript releases only apply to the following Backstage main-line release, not to the current one.
For anyone maintaining their own Backstage project, this means that you should strive to bump to the latest TypeScript version at least every 6 months, or you may encounter breakages as you upgrade Backstage packages. If you encounter any issues in doing so, please [file an issue in the main Backstage repository](https://github.com/backstage/backstage/issues/new/choose), as per this policy we should always support the latest version. In order to ensure that we do not start using new TypeScript features too early, the Backstage project itself uses the version at the beginning of the currently supported window, in the above example that would be version 4.8.
## PostgreSQL Releases
The Backstage project recommends and supports using PostgreSQL for persistent storage.
The PostgreSQL [versioning policy](https://www.postgresql.org/support/versioning/) is to release a new major version every year with new features which is then supported for 5 years after its initial release.
Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 12 to 16, then we would only test 12 and 16 explicitly.
+3 -4
View File
@@ -6,25 +6,24 @@ description: Details of the new backend system
## Status
The new backend system is in alpha, and some plugins do not yet fully implement it. But do feel free to try it out! We would love to hear back about your impressions.
The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system.
You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next).
## Overview
The new Backstage backend system is being built to help make it simpler to install backend plugins and to keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself with minimal disruption or cause for breaking changes. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611).
The new Backstage backend system was built to help make it simpler to install backend plugins and to keep projects up to date. It also changed the foundation to one that makes it a lot easier to evolve plugins and the system itself with minimal disruption or cause for breaking changes. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611).
One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system:
```ts
import { createBackend } from '@backstage/backend-defaults';
import { catalogPlugin } from '@backstage/plugin-catalog-backend';
// Create your backend instance
const backend = createBackend();
// Install all desired features
backend.add(catalogPlugin());
backend.add(import('@backstage/plugin-catalog-backend'));
// Start up the backend
await backend.start();
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
---
id: v1.21.0
title: v1.21.0
description: Backstage Release v1.21.0
---
These are the release notes for the v1.21.0 release of [Backstage](https://backstage.io/).
A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done.
## Highlights
### New Frontend System Alpha
This release marks the alpha release of the new frontend system, which has been in an experimental state since implementation began in the middle of 2023. This new system brings declarative integration of plugins, which is the ability to integrate new features into a Backstage app without writing any TypeScript code. Through this capability it also paves the way for supporting dynamic plugin installation at runtime.
The alpha release is a point of increased stability following the earlier experimental phase. There is now a complete system that lets you build out a full application, supported by [documentation](https://backstage.io/docs/frontend-system/). From this point on any breaking changes will also be clearly marked in the changelog.
There is still a long road ahead to a stable release, and this is not the time to migrate existing applications. There are only a few plugins that support this system so far, and if you want to add to a plugin that you own, please do so under an `/alpha` sub-path export.
Still, we encourage you to explore this new system to see whether you are confident in this path forward. If you have feedback or want to know more you can reach out in the #declarative-integration channel on Discord, or join an [Adoption SIG](https://github.com/backstage/community/blob/main/sigs/sig-adoption/README.md) meeting where the new system is frequently discussed. You can also check out our [maintainer talk at KubeCon NA 2023](https://youtu.be/ONMBYnhxnNU?t=436), where we talk about this new system and show a couple of demos.
### React Router Beta deprecation
This release of Backstage officially deprecates, but does _not_ immediately remove, support for old beta versions of [`react-router` 6](https://reactrouter.com/). Actual support for beta versions will be removed entirely in an upcoming release of Backstage. Please upgrade your own Backstage project as soon as possible to a stable version of `react-router`, by [following this guide](https://backstage.io/docs/tutorials/react-router-stable-migration/).
### New PostgreSQL versioning policy
The Backstage project has now settled on [a clearer policy](https://backstage.io/docs/overview/versioning-policy/#postgresql-releases) for what versions of PostgreSQL that it supports. In short, we support the last five [released major versions](https://www.postgresql.org/support/versioning/), and actively test against the first and last of those five, in a rolling window over time.
As part of this, the `TestDatabases` utility class now supports all of the last major versions of PostgreSQL in addition to the ones it supported before. You can also call `TestDatabases.setDefaults` inside your `setupTests.ts` file to configure the set of engines to test against, instead of enumerating them in every individual test.
Contributed by [@awanlin](https://github.com/awanlin) in [#21510](https://github.com/backstage/backstage/pull/21510)
### `UnifiedTheme` Now Supports Overrides
You can now supply overrides for Backstage components when using `createUnifiedTheme`. We've updated the demo sites Aperture theme to work with this and you can see the code for that [here](https://github.com/backstage/demo/blob/402cbb358cddacd59b339580bef0a4c5c2c7e013/packages/app/src/theme/aperture.ts#L85).
If you are switching from the old way of defining a theme to `createUnifiedTheme`, note that it uses the MUI v5 overrides format. The style overrides are now nested in a `styleOverrides` key, and if you want access to the theme youll need to use a callback:
```ts
BackstageHeaderTabs: {
styleOverrides: {
defaultTab: {
textTransform: 'none',
},
},
},
MuiChip: {
styleOverrides: {
root: ({ theme }) => ({
color: theme.palette.primary.dark,
}),
},
},
```
### Catalog pagination
`CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data.
To activate the pagination mode, simply update your `App.tsx` as follows:
```diff
const routes = (
<FlatRoutes>
...
- <Route path="/catalog" element={<CatalogIndexPage />} />
+ <Route path="/catalog" element={<CatalogIndexPage pagination />} />
...
```
In case you have a custom catalog page and you want to enable pagination, you need to pass the `pagination` prop to `EntityListProvider` instead. For now both column sorting and search filtering are still done locally, meaning they only apply to each individual page. This is something we will improve in the future and we still wanted to make this feature available early as it can greatly improve the performance of the catalog page.
### Azure DevOps Multi-Org Support
The Azure DevOps plugin now has multi-org support and there is a new processor to help with adding the needed annotations. Contributed by [@awanlin](https://github.com/awanlin) in [#19622](https://github.com/backstage/backstage/issues/19622)
### New Authentication providers
A new Atlassian authentication provider has been added to `@backstage/plugin-auth-backend`. Contributed by [@handsamtw](https://github.com/handsamtw) in [#21007](https://github.com/backstage/backstage/pull/21007)
A new VMware Cloud authentication provider has been added to `@backstage/plugin-auth-backend`. Contributed by [@luchillo17](https://github.com/luchillo17) in [#21337](https://github.com/backstage/backstage/pull/21337)
### Kubernetes single cluster selection
You can now select a `single` kubernetes cluster that the entity is part of from all your defined kubernetes clusters, by providing the `backstage.io/kubernetes-cluster` annotation with the defined cluster name.
If you do not specify the annotation then by default it fetches all defined kubernetes clusters.
To apply, update your `catalog-info.yaml`as follows:
```diff
metadata:
annotations:
'backstage.io/kubernetes-id': dice-roller
'backstage.io/kubernetes-namespace': dice-space
+ 'backstage.io/kubernetes-cluster': dice-cluster
'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end'
```
Contributed by [@deepan10](https://github.com/deepan10) in [#20954](https://github.com/backstage/backstage/pull/20954)
### BREAKING: Repo tools generated API Reports path changes
API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones.
### PagerDuty plugin changes home 🏡
The [PagerDuty](https://www.pagerduty.com/) plugin has been marked as deprecated in favor of [pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) which is maintained by PagerDuty themselves! We encourage you to [migrate to @pagerduty/backstage-plugin](https://pagerduty.github.io/backstage-plugin-docs/migration/) in order to receive future updates.
Congrats to the PagerDuty folks for taking ownership of the plugin 👏
Contributed by [@t1agob](https://github.com/t1agob) in [#21436](https://github.com/backstage/backstage/pull/21436)
## Security Fixes
This release does not contain any security fixes.
## Upgrade path
We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated).
## Links and References
Below you can find a list of links and references to help you learn about and start using this new release.
- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/)
- [GitHub repository](https://github.com/backstage/backstage)
- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy)
- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support
- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.21.0-changelog.md)
- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins)
Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage.
File diff suppressed because it is too large Load Diff