@spotify/backstage-plugin-soundcheck 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +130 -0
- package/dist/esm/CertificationSidebar-674faa0f.esm.js +2 -0
- package/dist/esm/EntitySoundcheckContent-ac9f00dd.esm.js +2 -0
- package/dist/esm/RefetchingIndicator-d7c86229.esm.js +2 -0
- package/dist/esm/SoundcheckQueryClientProvider-68e193be.esm.js +2 -0
- package/dist/esm/index-5f424c4c.esm.js +2 -0
- package/dist/esm/index-7e30ab1c.esm.js +2 -0
- package/dist/esm/index-a963da8e.esm.js +161 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.esm.js +2 -0
- package/package.json +76 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Commercial License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Spotify. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software is part of the "Spotify Plugins for Backstage" bundle. "Spotify
|
|
6
|
+
Plugins for Backstage" is commercial software. To use it, you must obtain a
|
|
7
|
+
license and agree to the [License
|
|
8
|
+
Terms](https://backstage.spotify.com/spotify-plugins-for-backstage-terms).
|
|
9
|
+
Commercial licenses can be obtained at https://backstage.spotify.com/.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Spotify Plugins for Backstage: Soundcheck
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Soundcheck incentivizes quality, reliability, and alignment of your software ecosystem. With Soundcheck, engineering organizations define development and operational standards, and measure the health of software components. Soundcheck provides teams with strong guidance to cultivate behavior and improve an organization's DevOps practices. Prioritize and visualize tech health and alignment to organizational best practices within Backstage.
|
|
6
|
+
|
|
7
|
+
There are 5 fundamental elements that make up Soundcheck:
|
|
8
|
+
|
|
9
|
+
- **Check**: A standard or best practice a component is graded against.
|
|
10
|
+
- **Check Result**: The result of running a check against a component. Results are either pass or fail.
|
|
11
|
+
- **Program**: A long-term tech health initiative.
|
|
12
|
+
- **Level**: A group of checks that represent a milestone within a program.
|
|
13
|
+
- **Certification**: The outcome of passing all checks within a level.
|
|
14
|
+
|
|
15
|
+
Together, they show you how any given software component is performing against your organization's long-term tech health initiatives.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
### 1. Install the Soundcheck backend plugin
|
|
20
|
+
|
|
21
|
+
You must install the [Soundcheck backend plugin](https://npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend) and all of its prerequisites before installing the Soundcheck frontend plugin.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
The next steps describe how to install the Soundcheck frontend plugin.
|
|
26
|
+
|
|
27
|
+
### 1. Install the plugin
|
|
28
|
+
|
|
29
|
+
1. Add the Soundcheck packages as dependencies to your Backstage instance
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
yarn workspace app add @spotify/backstage-plugin-soundcheck
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Install Soundcheck Entity Content Page & Card
|
|
36
|
+
|
|
37
|
+
The Soundcheck Entity Page consists of a view on the [Catalog Entity page](https://github.com/backstage/backstage/blob/master/packages/app/src/components/catalog/EntityPage.tsx), and lists
|
|
38
|
+
all the related certifications, levels, checks and check results for a
|
|
39
|
+
particular entity.
|
|
40
|
+
|
|
41
|
+
The code below adds the Soundcheck card to the overview tab for all component types.
|
|
42
|
+
The Soundcheck entity content component needs to be added to each relevant
|
|
43
|
+
page type within your Backstage `EntityPage`. The snippets below insert the
|
|
44
|
+
card/tabs at the end of their respective containers, but it's fine to reorder
|
|
45
|
+
them as you wish. When reordering the card in particular, consider whether the
|
|
46
|
+
[fluid layout](https://v4.mui.com/components/grid/#fluid-grids) of the grid
|
|
47
|
+
should be adjusted to ensure the cards fill each row.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// packages/app/src/components/catalog/EntityPage.tsx
|
|
51
|
+
|
|
52
|
+
import {
|
|
53
|
+
EntitySoundcheckContent,
|
|
54
|
+
EntitySoundcheckCard,
|
|
55
|
+
} from '@spotify/backstage-plugin-soundcheck';
|
|
56
|
+
|
|
57
|
+
// ...
|
|
58
|
+
|
|
59
|
+
const overviewContent = (
|
|
60
|
+
<Grid container spacing={3} alignItems="stretch">
|
|
61
|
+
{/* existing cards... */}
|
|
62
|
+
|
|
63
|
+
<Grid item md={6} xs={12}>
|
|
64
|
+
<EntitySoundcheckCard />
|
|
65
|
+
</Grid>
|
|
66
|
+
</Grid>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// ...
|
|
70
|
+
|
|
71
|
+
// Repeat this for all component entity pages which use the `overviewContent`
|
|
72
|
+
const serviceEntityPage = (
|
|
73
|
+
<EntityLayout>
|
|
74
|
+
{/* existing tabs... */}
|
|
75
|
+
|
|
76
|
+
<EntityLayout.Route path="/soundcheck" title="Soundcheck">
|
|
77
|
+
<EntitySoundcheckContent />
|
|
78
|
+
</EntityLayout.Route>
|
|
79
|
+
</EntityLayout>
|
|
80
|
+
);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 3. Install Soundcheck Overview Page
|
|
84
|
+
|
|
85
|
+
Add a new Route element with the path `/soundcheck` and element of `<SoundcheckOverviewPage />`. The route should look something like this:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// packages/app/src/App.tsx
|
|
89
|
+
import { SoundcheckOverviewPage } from '@spotify/backstage-plugin-soundcheck';
|
|
90
|
+
|
|
91
|
+
const routes = (
|
|
92
|
+
<FlatRoutes>
|
|
93
|
+
{/* existing routes... */}
|
|
94
|
+
|
|
95
|
+
<Route path="/soundcheck" element={<SoundcheckOverviewPage />} />
|
|
96
|
+
</FlatRoutes>
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 4. Setup sidebar item
|
|
101
|
+
|
|
102
|
+
Add a sidebar menu item that routes to the path setup in the previous step
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// packages/app/src/components/Root.tsx
|
|
106
|
+
import DoneAllIcon from '@material-ui/icons/DoneAll';
|
|
107
|
+
|
|
108
|
+
export const Root = ({ children }: PropsWithChildren<{}>) => (
|
|
109
|
+
<SidebarPage>
|
|
110
|
+
<Sidebar>
|
|
111
|
+
<SidebarLogo />
|
|
112
|
+
{/* existing sidebar items... */}
|
|
113
|
+
|
|
114
|
+
<SidebarScrollWrapper>
|
|
115
|
+
{/* existing sidebar items... */}
|
|
116
|
+
|
|
117
|
+
<SidebarItem icon={DoneAllIcon} to="soundcheck" text="Soundcheck" />
|
|
118
|
+
</SidebarScrollWrapper>
|
|
119
|
+
</Sidebar>
|
|
120
|
+
</SidebarPage>
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 5. Check everything is working
|
|
125
|
+
|
|
126
|
+
If you have followed all steps up to this point, Soundcheck is set up and
|
|
127
|
+
running. The backend successfully starts up if the program config is valid, and
|
|
128
|
+
when you navigate to a catalog page for one of the entity types you configured
|
|
129
|
+
above, you'll see the Soundcheck tab containing the applicable programs for the
|
|
130
|
+
current entity. If you visit `/soundcheck` or click the "Soundcheck" entry on the sidebar, you should see the overview page.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{stringifyEntityRef as b}from"@backstage/catalog-model";import{useApi as S,useRouteRef as _}from"@backstage/core-plugin-api";import{useQuery as k}from"@tanstack/react-query";import{s as N,a as B}from"./index-a963da8e.esm.js";import t,{useState as D,useEffect as F}from"react";import{makeStyles as d,Tooltip as O,Typography as r,alpha as p,Box as U}from"@material-ui/core";import{useEntity as M}from"@backstage/plugin-catalog-react";import{Link as W}from"react-router-dom";import G from"@material-ui/icons/Schedule";import{DateTime as g}from"luxon";import H from"react-use/lib/useInterval";import j from"classnames";import{C as P,L as T,N as Q,F as f,A as q}from"./SoundcheckQueryClientProvider-68e193be.esm.js";import{Skeleton as c}from"@material-ui/lab";import{Link as J}from"@backstage/core-components";function K(e){const a=S(N),n=b(e);return k(["soundcheck/certifications",n],async()=>a.getAllCertifications(n))}function C(e,a){const n=S(N),i=b(e);return k(["soundcheck/certifications",i,a],async()=>n.getCertificationDetails(i,a),{enabled:!!a})}const V=d(e=>({root:{display:"inline-flex",alignItems:"center",gap:e.spacing(1)}})),X=1,Y=60*1e3;function y(e){const a=g.fromISO(e);return g.now().diff(a).as("minutes")<X?"now":a.toRelative()}const Z=e=>{const[a,n]=D(y(e));return F(()=>{n(y(e))},[e]),H(()=>n(y(e)),Y),a},L=({timestamp:e,description:a})=>{const n=V(),i=g.fromISO(e).toLocaleString(g.DATETIME_FULL),l=Z(e),o=a?`${a}: ${l}`:void 0;return t.createElement("div",{className:n.root},t.createElement(O,{title:i},t.createElement(r,{variant:"caption","aria-label":o},l)),t.createElement(G,null))},h=d(e=>({root:{display:"grid",width:"100%",gridTemplateColumns:"auto 1fr auto",gridColumnGap:e.spacing(1),padding:e.spacing(1),alignItems:"center","&.selected":{backgroundColor:p(e.palette.primary[e.palette.type],.2)},"&:hover, &:active, &:focus":{backgroundColor:p(e.palette.primary[e.palette.type],.3)}}})),ee=({className:e,href:a,children:n})=>a?t.createElement(W,{to:a,className:e},n):t.createElement("div",{className:e},n),te=({result:e,name:a,timestamp:n,selected:i=!1,href:l})=>{const o=h(),E=j(o.root,{selected:i});return t.createElement(ee,{href:l,className:E},t.createElement(P,{result:e}),t.createElement(r,{variant:"body2"},a),n?t.createElement(L,{timestamp:n}):null)},R=d(e=>{const a=e.palette.type==="dark"?e.palette.grey[700]:e.palette.grey[100];return{wrapper:{backgroundColor:a,color:p(e.palette.getContrastText(a),.7),fontSize:e.typography.caption.fontSize,minHeight:"auto",borderTop:`1px solid ${e.palette.divider}`,borderBottom:`1px solid ${e.palette.divider}`,padding:e.spacing(1),display:"grid",gridTemplateAreas:({badge:n})=>[`"${n?"badge":"title"} title"`,`"${n?".":"description"} description"`].join(" "),gridTemplateColumns:"auto 1fr"},title:{gridArea:"title",textTransform:"uppercase",fontWeight:"bold",color:p(e.palette.getContrastText(a),.7),fontSize:e.typography.body2.fontSize,paddingTop:e.spacing(.5),paddingBottom:e.spacing(.5),lineHeight:1},badge:{gridArea:"badge",marginRight:e.spacing(1)},description:{gridArea:"description",color:p(e.palette.getContrastText(a),.7),fontSize:e.typography.subtitle2.fontSize,marginTop:e.spacing(1)}}}),ae=e=>{const a=R({badge:e.badge});return t.createElement("div",{className:a.wrapper},e.badge?t.createElement(T,{className:a.badge,badge:e.badge}):null,t.createElement(r,{className:a.title},e.title),e.description?t.createElement(r,{className:a.description},e.description):null)},x=d(e=>({root:{padding:e.spacing(2),margin:0,display:"grid",gridTemplateColumns:"min-content auto",gridGap:e.spacing(2)},title:{fontSize:e.typography.pxToRem(18)},level:{textTransform:"uppercase",color:e.palette.text.secondary,fontWeight:700,letterSpacing:"1px"}}));function ne({description:e,documentationUrl:a}){return a?t.createElement(r,{variant:"body2"},e," ",t.createElement(J,{to:a},"Learn more")):t.createElement(r,{variant:"body2"},e)}const A=({name:e,level:a,badge:n,description:i,documentationUrl:l})=>{const o=x();return t.createElement("div",{className:o.root},n?t.createElement(T,{size:"large",badge:n}):t.createElement(Q,{size:"large"}),t.createElement("div",null,t.createElement(r,{variant:"caption",className:o.level},a??"No Level"),t.createElement(r,{variant:"h4",className:o.title},e),i&&t.createElement(ne,{description:i,documentationUrl:l})))},$=({hideDescription:e=!1})=>{const a=x();return t.createElement(f,null,t.createElement("div",{className:a.root},t.createElement(c,{width:44,height:44}),t.createElement("div",null,t.createElement(r,{variant:"caption",className:a.level},t.createElement(c,{width:100})),t.createElement(r,{variant:"h4",className:a.title},t.createElement(c,{width:300})),!e&&t.createElement(r,{variant:"body2"},t.createElement(c,null)))))},re=()=>{const e=R({});return t.createElement(f,null,t.createElement("div",{className:e.wrapper},t.createElement(c,{className:e.title}),t.createElement(r,{className:e.description},t.createElement(c,null))))},v=()=>{const e=h();return t.createElement(f,null,t.createElement("div",{className:e.root},t.createElement(c,{width:24,height:24}),t.createElement(r,{variant:"body2"},t.createElement(c,null)),t.createElement(c,{width:100,height:24})))},ie=()=>{const e=I();return t.createElement("div",{className:e.root},t.createElement($,null),new Array(3).fill(null).map((a,n)=>t.createElement(t.Fragment,{key:`skeleton-level-${n}`},t.createElement(re,null),t.createElement("ul",{className:e.checks},t.createElement(v,null),t.createElement(v,null),t.createElement(v,null)))))},I=d(e=>({root:{borderRight:`1px solid ${e.palette.divider}`},checks:{padding:0,margin:0,flex:1,listStyle:"none"},checkItem:{borderBottom:`1px solid ${e.palette.divider}`,"&:last-of-type":{borderBottom:"0"}}})),le=({programId:e,checkId:a})=>{var n,i;const l=I(),{entity:o}=M(),E=_(B),{data:s,isLoading:w,isError:z}=C(o,e);return z?t.createElement("div",{className:l.root},t.createElement(U,{padding:2},t.createElement(q,{severity:"error",title:"Error loading certification"}))):w||!e?t.createElement(ie,null):s?t.createElement("div",{className:l.root},t.createElement(A,{name:s.program.name,level:(n=s.highestLevel)==null?void 0:n.name,badge:(i=s.highestLevel)==null?void 0:i.badge,description:s.program.description,documentationUrl:s.program.documentationUrl}),s?.levels.map(m=>t.createElement(t.Fragment,{key:m.ordinal},t.createElement(ae,{key:m.ordinal,badge:m.badge,title:m.name,description:m.description}),t.createElement("ul",{className:l.checks},m.checks.map(u=>t.createElement("li",{key:u.id,className:l.checkItem},t.createElement(te,{...u,selected:u.id===a,href:E({programId:e,checkId:u.id})}))))))):null};export{le as C,L as R,K as a,$ as b,A as c,C as u};
|
|
2
|
+
//# sourceMappingURL=CertificationSidebar-674faa0f.esm.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e,{useState as I,useEffect as M}from"react";import{Link as W,useParams as O,Routes as Q,Route as y,Navigate as P}from"react-router-dom";import{makeStyles as d,Typography as h,Box as j,Tab as x,Tabs as T,Paper as H,Divider as U}from"@material-ui/core";import{SpotifyLicenseBanner as V}from"@spotify/backstage-plugin-core";import{useEntity as p}from"@backstage/plugin-catalog-react";import{R as Y}from"./RefetchingIndicator-d7c86229.esm.js";import{R as q,u as L,a as k,C as G}from"./CertificationSidebar-674faa0f.esm.js";import{Skeleton as s}from"@material-ui/lab";import{MarkdownContent as B}from"@backstage/core-components";import{stringifyEntityRef as J}from"@backstage/catalog-model";import{useApi as K,useRouteRef as C,useRouteRefParams as X}from"@backstage/core-plugin-api";import{useQuery as Z}from"@tanstack/react-query";import{s as ee,R as c,c as v,a as F}from"./index-a963da8e.esm.js";import{A as b,F as A,L as te,N as ne,a as ae,S as re}from"./SoundcheckQueryClientProvider-68e193be.esm.js";import ie from"react-use/lib/useMeasure";import D from"react-use/lib/usePrevious";import oe from"react-confetti";import"react-use/lib/useDebounce";import"@material-ui/icons/Schedule";import"luxon";import"react-use/lib/useInterval";import"classnames";import"graphql-request";import"graphql-tag";import"@material-ui/icons/Check";import"@material-ui/icons/Close";import"@material-ui/icons/RemoveCircleOutline";import"@material-ui/icons/HelpOutline";import"lodash";function le(t,a,n){const r=K(ee),i=J(t);return Z(["soundcheck/check-details",i,a,n],async()=>r.getCheckResultDetails(i,a,n),{enabled:!!a&&!!n})}const se=d({markdownContent:{"& :last-child":{marginBottom:0}}}),ce=({state:t,notes:a})=>{const n=se(),r={[c.Passed]:"success",[c.Failed]:"error",[c.NotReported]:"info",[c.NotApplicable]:"info"}[t],i={[c.Passed]:"Check passed",[c.Failed]:"Check did not pass",[c.NotReported]:"Check not reported",[c.NotApplicable]:"Check not applicable"}[t];return e.createElement(b,{severity:r,title:i},a?e.createElement(B,{className:n.markdownContent,content:a}):null)},me=()=>{const t=$();return e.createElement(A,null,e.createElement("div",{className:t.root,"data-testid":"check-details-view"},e.createElement("div",{className:t.topBar},e.createElement(h,{variant:"h2",className:t.title},e.createElement(s,{width:300,height:32})),e.createElement(s,{width:100,height:32})),e.createElement(s,{variant:"rect",height:120}),e.createElement("div",{"data-testid":"soundcheck-check-details-description"},e.createElement(h,{variant:"h3"},e.createElement(s,null)),e.createElement(s,null),e.createElement(s,null),e.createElement(s,null))))},$=d(t=>({root:{padding:`${t.spacing(3)}px ${t.spacing(5)}px`},title:{fontWeight:"normal",fontSize:t.typography.h5.fontSize},description:{padding:`${t.spacing(3)}px 0`},subtitle:{fontWeight:"normal",fontSize:t.typography.h6.fontSize},topBar:{display:"flex",justifyContent:"space-between",marginBottom:t.spacing(2)}})),ue=({programId:t,checkId:a})=>{const n=$(),{entity:r}=p(),{data:i,isLoading:o,isError:l}=le(r,t,a);if(l)return e.createElement(j,{padding:2},e.createElement(b,{severity:"error",title:"Error loading check details"}));if(o||!t||!a)return e.createElement(me,null);if(!i)return null;const{name:f,description:u,result:g,timestamp:m,notes:E}=i;return e.createElement("div",{className:n.root,"data-testid":"check-details-view"},e.createElement("div",{className:n.topBar},e.createElement(h,{variant:"h2",className:n.title},f),m?e.createElement(q,{timestamp:m,description:"Last updated"}):null),e.createElement(ce,{state:g,notes:E}),e.createElement("div",{className:n.description,"data-testid":"soundcheck-check-details-description"},e.createElement(h,{variant:"h3",className:n.subtitle},"Description"),e.createElement(B,{content:u})))},de=d(()=>({root:{position:"absolute",width:"100%",height:"100%"}})),pe=({programId:t})=>{var a,n;const r=de(),{entity:i}=p(),{data:o,isLoading:l}=L(i,t),[f,{width:u,height:g}]=ie(),m=l?void 0:(n=(a=o?.highestLevel)==null?void 0:a.ordinal)!=null?n:-1,E=D(m),S=D(t),[z,N]=I(!1);return M(()=>{typeof E<"u"&&typeof m<"u"&&t===S&&m>E&&N(!0)},[m,E,t,S]),z?e.createElement("div",{ref:f,className:r.root},e.createElement(oe,{width:u,height:g,numberOfPieces:1e3,gravity:1,initialVelocityY:20,recycle:!1,onConfettiComplete:()=>N(!1)})):null},_=d(t=>({root:{maxWidth:"80ch",textTransform:"uppercase",paddingTop:t.spacing(2),paddingBottom:t.spacing(2)},wrapper:{display:"inline-block",textOverflow:"ellipsis",whiteSpace:"nowrap",overflow:"hidden"},badge:{marginRight:t.spacing(1)}})),fe=({id:t,name:a,badge:n,href:r,selected:i=!1})=>{const o=_();return e.createElement(x,{className:o.root,classes:{wrapper:o.wrapper},value:t,label:e.createElement(e.Fragment,null,n?e.createElement(te,{badge:n,className:o.badge}):e.createElement(ne,{className:o.badge}),a),component:W,to:r,selected:i})},R=()=>{const t=_();return e.createElement(x,{className:t.root,classes:{wrapper:t.wrapper},label:e.createElement(s,{width:180})})},Ee=()=>e.createElement(A,null,e.createElement(T,{value:!1,indicatorColor:"primary"},e.createElement(R,null),e.createElement(R,null),e.createElement(R,null)));function he(t=[],a){const n=t.findIndex(r=>r.program.id===a);return n<0?!1:n}const ve=({programId:t})=>{const{entity:a}=p(),{isLoading:n,data:r}=k(a),i=C(v);if(n||!t)return e.createElement(Ee,null);const o=he(r,t);return e.createElement(T,{value:o,indicatorColor:"primary","aria-label":"Certifications",variant:"scrollable"},r&&r.map(({program:{id:l,name:f},highestLevel:u})=>e.createElement(fe,{key:l,id:l,name:f,badge:u?.badge,selected:l===t,href:i({programId:l})})))},ge=d({paper:{overflow:"hidden",position:"relative"},view:{display:"grid",gridTemplateColumns:"1fr 2fr"}}),ye=()=>{const{programId:t,checkId:a}=O(),n=ge(),{entity:r}=p(),{isError:i,isFetched:o,data:l}=k(r);return i?e.createElement(b,{severity:"error",title:"Error loading certifications"}):o&&!l?.length?e.createElement(ae,null):e.createElement(H,{className:n.paper},e.createElement(Y,null),e.createElement(ve,{programId:t}),e.createElement(U,null),e.createElement("div",{"data-testid":"soundcheck-certification-view",className:n.view},e.createElement(G,{programId:t,checkId:a}),e.createElement(ue,{programId:t,checkId:a}),e.createElement(pe,{programId:t})))},ke=d(t=>({root:{"&:not(:first-child)":{marginTop:t.spacing(2)}}})),w=()=>{const t=ke();return e.createElement(e.Fragment,null,e.createElement(V,{backend:"soundcheck",invalidLicenseMessage:"Submitted check results will still be stored, but it will not be possible to view certifications or individual results until a valid license is present.",inline:!0}),e.createElement("div",{className:t.root},e.createElement(ye,null)))},Ce=()=>{var t;const{entity:a}=p(),n=C(v),{data:r}=k(a),i=(t=r?.[0])==null?void 0:t.program.id;return i?e.createElement(P,{to:n({programId:i}),replace:!0}):null},be=()=>{var t,a;const{entity:n}=p(),{programId:r}=X(v),i=C(F),{data:o}=L(n,r),l=(a=(t=o?.levels[0])==null?void 0:t.checks[0])==null?void 0:a.id;return l?e.createElement(P,{to:i({programId:r,checkId:l}),replace:!0}):null},Re=()=>e.createElement(Q,null,e.createElement(y,{path:"/",element:e.createElement(e.Fragment,null,e.createElement(w,null),e.createElement(Ce,null))}),e.createElement(y,{path:v.path,element:e.createElement(e.Fragment,null,e.createElement(w,null),e.createElement(be,null))}),e.createElement(y,{path:F.path,element:e.createElement(w,null)})),we=()=>e.createElement(re,null,e.createElement(Re,null));export{we as EntitySoundcheckContent};
|
|
2
|
+
//# sourceMappingURL=EntitySoundcheckContent-ac9f00dd.esm.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import t,{useState as n}from"react";import{makeStyles as s,LinearProgress as a}from"@material-ui/core";import m from"react-use/lib/useDebounce";import"@backstage/catalog-model";import"@backstage/core-plugin-api";import{useIsFetching as c}from"@tanstack/react-query";import"./index-a963da8e.esm.js";import"@backstage/plugin-catalog-react";import"react-router-dom";import{F as d}from"./SoundcheckQueryClientProvider-68e193be.esm.js";const l=s(e=>({indicator:{position:"absolute",width:"100%",zIndex:e.zIndex.speedDial}})),p=()=>{const e=c(),i=l(),[r,o]=n(!!e);return m(()=>{o(!!e)},250,[e]),r?t.createElement(d,null,t.createElement("div",{className:i.indicator},t.createElement(a,{variant:"indeterminate","data-testid":"refetching-indicator"}))):null};export{p as R};
|
|
2
|
+
//# sourceMappingURL=RefetchingIndicator-d7c86229.esm.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import r,{useState as u,useEffect as f}from"react";import{Fade as h,withStyles as s,makeStyles as l,Typography as y}from"@material-ui/core";import b from"@material-ui/icons/Check";import g from"@material-ui/icons/Close";import E from"@material-ui/icons/RemoveCircleOutline";import C from"@material-ui/icons/HelpOutline";import{R as n}from"./index-a963da8e.esm.js";import i from"classnames";import{Alert as S}from"@material-ui/lab";import{EmptyState as m}from"@backstage/core-components";import{humanizeEntityRef as N}from"@backstage/plugin-catalog-react";import{parseEntityRef as v}from"@backstage/catalog-model";import{QueryClient as x,QueryClientProvider as I}from"@tanstack/react-query";import{memoize as k}from"lodash";import{useApi as A,errorApiRef as F}from"@backstage/core-plugin-api";const R=({children:e})=>{const[t,o]=u(!1);return f(()=>{const a=setTimeout(()=>{o(!0)},250);return()=>clearTimeout(a)},[]),r.createElement(h,{in:t,timeout:250},e)},T=e=>({[n.Passed]:"Check passed",[n.NotReported]:"Check not reported",[n.Failed]:"Check failed",[n.NotApplicable]:"Check not applicable"})[e],w=s(e=>({root:{color:e.palette.success.main}}))(b),$=s(e=>({root:{color:e.palette.error.main}}))(g),B=s(e=>({root:{color:e.palette.info.main}}))(C),P=s(e=>({root:{color:e.palette.text.disabled}}))(E),L=({result:e,className:t})=>{const o={className:t,"aria-label":T(e),"aria-hidden":!1};return e===n.Passed?r.createElement(w,{...o}):e===n.NotReported?r.createElement(B,{...o}):e===n.Failed?r.createElement($,{...o}):e===n.NotApplicable?r.createElement(P,{...o}):null},p=24,O=11,c=e=>e==="small"?1:2,D=l({root:{position:"relative",display:"inline-flex",alignItems:"center",justifyContent:"center",borderRadius:"50%",borderWidth:"2px",fontWeight:700,width:({size:e})=>`${p*c(e)}px`,height:({size:e})=>`${p*c(e)}px`,fontSize:({size:e})=>`${O*c(e)}px`}}),d=({className:e,label:t,size:o="small"})=>{const a=D({size:o});return r.createElement("span",{className:i(e,a.root),role:"img","aria-label":`${t} badge`},t)},M=l(e=>({root:{background:({color:t})=>t,color:({color:t})=>e.palette.getContrastText(t),borderColor:e.palette.common.black,borderStyle:"solid","&::after":{position:"absolute",display:"block",content:'""',top:0,left:0,width:"100%",height:"100%",borderRadius:"50%",boxShadow:["inset 0 -0.18em 0 0 rgba(0, 0, 0, 0.25)","inset 0px 0.18em 0px 0px rgba(255, 255, 255, 0.5)","inset 0px 1.3em 0px -0.5em rgba(255, 255, 255, 0.2)"].join(",")}}})),W=({className:e,badge:t,size:o="small"})=>{const a=M({color:t.options.color,size:o});return r.createElement(d,{className:i(e,a.root),size:o,label:`L${t.options.level}`})},q=l({root:{borderColor:"currentColor",borderStyle:"dashed"}}),G=({className:e,size:t="small"})=>{const o=q();return r.createElement(d,{className:i(e,o.root),size:t,label:"NL"})},Q=l(e=>({root:{padding:e.spacing(3)},icon:{display:"none"},message:{padding:0},type:{fontWeight:700}})),_=e=>{const{type:t,...o}=Q();return r.createElement(S,{severity:e.severity,elevation:1,classes:o},r.createElement(y,{variant:"subtitle1",classes:{root:t}},e.title),e.children)},j=({className:e})=>r.createElement("div",{className:e},r.createElement(m,{title:"No certifications available",missing:"data",description:"There are no programs configured that apply to this entity."})),z=({ownerEntityRef:e})=>e?r.createElement(m,{missing:"data",title:"Missing entities",description:r.createElement(r.Fragment,null,"Looks like the group"," ",N(v(e),{defaultKind:"Group"})," ","doesn't own any entities.",r.createElement("br",null),r.createElement("br",null),"Please select another group from the dropdown in the corner of the header.")}):null,Z=k(e=>new x({defaultOptions:{queries:{refetchInterval:6e4,refetchIntervalInBackground:!1,refetchOnWindowFocus:"always",retry:2,retryDelay:t=>{const o=450+Math.ceil(Math.random()*100);return Math.min(o*2**t,3e4)},onError:t=>e.post(t)}}})),H=e=>{const t=A(F),o=Z(t);return r.createElement(I,{client:o},e.children)};export{_ as A,L as C,R as F,W as L,G as N,H as S,j as a,z as b};
|
|
2
|
+
//# sourceMappingURL=SoundcheckQueryClientProvider-68e193be.esm.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import t,{useState as C,useEffect as v,useMemo as N,useCallback as R,memo as b,Fragment as j}from"react";import{HeaderTabs as A,Link as P,EmptyState as M,Page as z,Header as H,Content as D}from"@backstage/core-components";import{SpotifyLicenseBanner as K}from"@spotify/backstage-plugin-core";import{makeStyles as g,alpha as O,TextField as Q,CircularProgress as V,Tooltip as U,LinearProgress as q,Typography as I,Grid as T}from"@material-ui/core";import{C as J,L as X,S as Y,b as Z,A as ee}from"./SoundcheckQueryClientProvider-68e193be.esm.js";import te from"@material-ui/lab/Autocomplete";import{stringifyEntityRef as L,parseEntityRef as x}from"@backstage/catalog-model";import{useApi as h,identityApiRef as re,useRouteRef as _}from"@backstage/core-plugin-api";import{useQuery as $}from"@tanstack/react-query";import{s as G,R as k}from"./index-a963da8e.esm.js";import{catalogApiRef as F,entityRouteRef as B,humanizeEntityRef as W}from"@backstage/plugin-catalog-react";import{useSearchParams as ae}from"react-router-dom";import{Skeleton as E}from"@material-ui/lab";import{R as ne}from"./RefetchingIndicator-d7c86229.esm.js";import{countBy as oe}from"lodash";import"@material-ui/icons/Check";import"@material-ui/icons/Close";import"@material-ui/icons/RemoveCircleOutline";import"@material-ui/icons/HelpOutline";import"classnames";import"graphql-request";import"graphql-tag";import"react-use/lib/useDebounce";function le(e){const a=h(G);return $(["soundcheck/facets-for-owner",e],async()=>a.getFacetsForOwner(e),{enabled:!!e})}function se(e,a){const n=h(G);return $(["soundcheck/program-overview-for-owner",e,a],async()=>n.getProgramOverviewForOwner(e,a),{enabled:!!e&&!!a?.type})}function ie(){const e=h(re),a=h(F);return $(["soundcheck/user-groups-claims"],async()=>{const{ownershipEntityRefs:n}=await e.getBackstageIdentity();return(await Promise.all(n.map(o=>a.getEntityByRef(o)))).filter(o=>o?.kind==="Group").map(o=>{var l,c,i,d;return{name:(d=(i=(c=(l=o.spec)==null?void 0:l.profile)==null?void 0:c.displayName)!=null?i:o.metadata.title)!=null?d:o.metadata.name,ref:L(o)}})})}function ce(){const e=h(F);return $(["soundcheck/all-groups"],async()=>{const{items:a}=await e.getEntities({filter:{kind:"Group"}});return a.filter(n=>n?.kind==="Group").map(n=>{var r,o,l,c;return{name:(c=(l=(o=(r=n.spec)==null?void 0:r.profile)==null?void 0:o.displayName)!=null?l:n.metadata.title)!=null?c:n.metadata.name,ref:L(n)}})})}const de=()=>{const[e,a]=ae(),n=Object.fromEntries(e.entries()),[r,o]=C(n);return v(()=>{a(r,{replace:!0})},[r,a]),[r,o]},pe=()=>{const{data:e,isLoading:a,isError:n}=ie(),{data:r,isLoading:o,isError:l}=ce();return{options:N(()=>{const i=[];return e&&i.push(...e.map(({name:d,ref:s})=>({name:d,ref:s,key:"My Groups"}))),r&&i.push(...r.map(({name:d,ref:s})=>({name:d,ref:s,key:"All Groups"}))),i},[e,r]),isLoading:a||o,isError:n||l}},ue=g(e=>({root:{width:"100%",minWidth:250},textField:{"& $notchedOutline":{borderColor:O(e.page.fontColor,.25)},"&:hover $notchedOutline":{borderColor:e.page.fontColor}},input:{backgroundColor:"transparent",color:e.page.fontColor},clearIndicator:{color:e.page.fontColor},popupIndicator:{color:e.page.fontColor},notchedOutline:{}})),me=e=>{const{onChange:a,initialValue:n,setError:r}=e,{options:o,isLoading:l,isError:c}=pe(),i=ue(),[d,s]=C(null);v(()=>{if(!l&&o?.length&&!d){const p=n?o.find(m=>m.ref===n):o[0];p&&(s(p),a?.(p.ref))}},[l,o,d,a,n]);const u=R((p,m)=>{s(m),a?.(m?.ref)},[a,s]);return v(()=>{r?.(c?new Error("Error loading groups"):void 0)},[c,r]),c?null:t.createElement(te,{"aria-label":"Current group",className:i.root,classes:{clearIndicator:i.clearIndicator,popupIndicator:i.popupIndicator},disableClearable:!0,options:o??[],loading:l,groupBy:p=>p.key,value:d,freeSolo:!1,onChange:u,getOptionLabel:p=>p.name,renderInput:p=>t.createElement(Q,{...p,variant:"outlined",placeholder:l?"Loading":"Select a group",className:i.textField,InputProps:{...p.InputProps,className:i.input,classes:{notchedOutline:i.notchedOutline},endAdornment:t.createElement(t.Fragment,null,l?t.createElement(V,{color:"inherit",size:20}):null,p.InputProps.endAdornment)}})})},ge=()=>{const e=[{label:t.createElement(E,{width:180})},{label:t.createElement(E,{width:180})},{label:t.createElement(E,{width:180})},{label:t.createElement(E,{width:180})}];return t.createElement(A,{tabs:e})},fe=g(e=>({root:{borderTop:`1px solid ${e.palette.divider}`,borderBottom:`1px solid ${e.palette.divider}`},tab:{textTransform:"uppercase",paddingTop:e.spacing(2),paddingBottom:e.spacing(2)}})),Ee=({facets:e,onChange:a,type:n})=>{var r,o;const l=fe(),c=R(s=>{var u;a((u=e?.types[s].value)!=null?u:"")},[e?.types,a]);v(()=>{var s;const u=e?.types&&e.types.length>=0,p=!n&&u,m=n&&u&&!e.types.find(({value:y})=>y===n);(p||m)&&a((s=e.types[0])==null?void 0:s.value)},[n,e?.types,a]);const i=(r=e?.types.map(({value:s,count:u})=>({key:s,label:`${s} (${u>50?"50+":u})`,id:s,tabProps:{className:l.tab}})))!=null?r:[];if(!i.length)return null;const d=(o=e?.types.findIndex(s=>s.value===n))!=null?o:-1;return t.createElement(t.Fragment,null,t.createElement(ne,null),t.createElement(A,{onChange:c,tabs:i,selectedIndex:d>-1?d:void 0}))},be=g(e=>({root:{height:"8px",minWidth:"64px",marginLeft:e.spacing(2),borderRadius:"100vh",backgroundColor:e.palette.background.default},bar:{backgroundColor:e.palette.success.main,transition:"none"}})),ye=e=>{var a,n;const r=be(e),o=N(()=>Object.entries(e.progress).reduce((c,[,i])=>c+i,0),[e.progress]);if(o===0)return null;const l=(a=e.progress[k.Passed])!=null?a:0;return t.createElement(U,{arrow:!0,title:`Check passing for ${l} of ${o} ${o===1?"entity":"entities"}`,enterDelay:0,placement:"top"},t.createElement(q,{variant:"determinate",value:((n=e.progress[k.Passed])!=null?n:0)/o*100,classes:{root:r.root,bar:r.bar}}))},ve=g(e=>({root:{padding:0},iconWrapper:{padding:e.spacing(1.5),display:"flex",alignItems:"center",justifyContent:"center"},link:{"&:hover $iconWrapper, &:active $iconWrapper, &:focus $iconWrapper":{backgroundColor:e.palette.infoBackground}},icon:{width:"0.75em",height:"0.75em"},empty:{width:e.spacing(2),height:e.spacing(2)}})),he=b(({programId:e,checkId:a,entityRef:n,result:r,entityRoute:o})=>{const l=ve();if(typeof n!="string")return t.createElement("td",{className:`${l.root}`,"aria-hidden":!0},t.createElement("div",{className:l.iconWrapper},t.createElement("div",{className:l.empty})));const c=t.createElement("div",{className:l.iconWrapper},t.createElement(J,{className:l.icon,result:r??k.NotReported})),i=r&&r===k.NotApplicable?c:t.createElement(P,{className:l.link,to:`${o(x(n))}/soundcheck/${e}/${a}`},c);return t.createElement("td",{className:l.root},i)}),ke=g(e=>({checkNameCell:{padding:0,backgroundColor:e.palette.background.paper},checkNameContent:{padding:`0 ${e.spacing(2)}px`,display:"flex",justifyContent:"space-between",alignItems:"center"},checkNameTypography:{overflow:"hidden",textOverflow:"ellipsis"},checkIndicator:{flexBasis:e.spacing(8)}})),we=b(({programId:e,programName:a,check:n,entityRefs:r,results:o,entityRoute:l})=>{const c=ke(),i=N(()=>oe(o.filter(({result:s})=>s!==k.NotApplicable),"result"),[o]),d=new Map(o.map(s=>[s.entityRef,s.result]));return t.createElement("tr",{"data-testid":"program-check-row"},t.createElement("th",{scope:"row",className:c.checkNameCell,"aria-label":`${n.name} check for ${a} program`},t.createElement("div",{className:c.checkNameContent},t.createElement(I,{className:c.checkNameTypography,variant:"subtitle2",component:"p"},n.name),t.createElement(ye,{className:c.checkIndicator,progress:i}))),r.map((s,u)=>t.createElement(he,{key:u,programId:e,checkId:n.id,entityRef:s,result:typeof s=="string"?d.get(s):void 0,entityRoute:l})))}),Ce=b(({entityRef:e,highestLevels:a})=>{const n=a.find(r=>r.entityRef===e);return n?.badge?t.createElement(X,{badge:n.badge,size:"small"}):null}),Ne=g(e=>({programNameCell:{padding:0,position:"sticky",backgroundColor:e.palette.background.paper},programNameContent:{padding:e.spacing(2)},programNameTypography:{overflow:"hidden",textOverflow:"ellipsis"},badgeCellContent:{display:"flex",justifyContent:"center",alignItems:"center"}})),xe=b(({program:e,entityRefs:a,highestLevels:n})=>{const r=Ne();return t.createElement("tr",{"data-testid":"program-title-row"},t.createElement("th",{scope:"row",className:r.programNameCell},t.createElement("div",{className:r.programNameContent},t.createElement(I,{className:r.programNameTypography,variant:"h5",component:"p"},e.name))),a.map((o,l)=>typeof o!="string"?t.createElement("td",{key:l,"aria-hidden":!0}):t.createElement("td",{key:l,"data-testid":"program-certification-cell"},t.createElement("div",{className:r.badgeCellContent},t.createElement(Ce,{entityRef:o,highestLevels:n})))))}),$e=g(e=>{const a=e.palette.type==="dark"?e.palette.grey[700]:e.palette.grey[100];return{root:{color:O(e.palette.getContrastText(a),.7),backgroundColor:a},levelContent:{padding:`${e.spacing(.5)}px ${e.spacing(2)}px`,backgroundColor:a},levelTypography:{overflow:"hidden",textOverflow:"ellipsis"}}}),Se=b(({level:e,entityRefs:a,programName:n})=>{const r=$e();return t.createElement("tr",{className:r.root,"data-testid":"program-level-row"},t.createElement("th",{scope:"row",className:r.root},t.createElement("div",{className:r.levelContent},t.createElement(I,{className:r.levelTypography,variant:"subtitle2",component:"p","aria-label":`${e.name} for ${n} program`},e.name))),a.map((o,l)=>t.createElement("td",{key:l,className:r.root,"aria-hidden":!0})))}),Pe=({program:e,highestLevels:a,levels:n,entityRefs:r,entityRoute:o})=>t.createElement("tbody",null,t.createElement(xe,{program:e,entityRefs:r,highestLevels:a}),n.map((l,c)=>t.createElement(j,{key:c},t.createElement(Se,{entityRefs:r,level:l,programName:e.name}),l.checks.map(({check:i,results:d},s)=>t.createElement(we,{key:s,check:i,programId:e.id,programName:e.name,entityRefs:r,results:d,entityRoute:o}))))),Ie=g(e=>({root:{position:"sticky",bottom:"-1px",backgroundColor:e.palette.background.default,boxShadow:`0 -1px ${e.palette.divider}`,"& td$cell, & th$cell":{border:0,padding:e.spacing(1)},"& td$cell":{backgroundColor:e.palette.background.default}},row:{boxShadow:`1px 0 ${e.palette.background.default}, -1px 0 ${e.palette.background.default}`},cell:{verticalAlign:"top"},cellInner:{textOrientation:"mixed",writingMode:"vertical-lr",transform:"rotate(-20deg)",transformOrigin:`100% ${e.spacing(1)}px`,wordBreak:"keep-all",fontWeight:"bold",overflow:"hidden",textOverflow:"ellipsis",maxHeight:"15vh"}})),Te=({entityRefs:e})=>{const a=_(B),n=Ie();return t.createElement("tfoot",{className:n.root,"data-testid":"results-table-footer"},t.createElement("tr",{className:n.row},t.createElement("td",{className:n.cell}),e.map((r,o)=>typeof r!="string"?t.createElement("th",{key:o,className:n.cell,"aria-hidden":!0}):t.createElement("th",{scope:"col",key:o,className:n.cell},t.createElement("div",{className:n.cellInner},t.createElement(P,{to:a(x(r))},W(x(r),{defaultKind:"component"})))))))},Re=g(e=>({table:{backgroundColor:e.palette.background.paper,borderCollapse:"collapse",whiteSpace:"nowrap","& th, & td":{border:`1px solid ${e.palette.divider}`,borderCollapse:"collapse"},paddingBottom:e.spacing(2)},programTitle:{padding:e.spacing(2)},checkResult:{padding:e.spacing(1.5)},title:{padding:`${e.spacing(1)}px ${e.spacing(2)}px`},header:{backgroundColor:e.palette.type==="dark"?e.palette.grey[700]:e.palette.grey[100]}})),Ae=()=>{const e=Re(),a=new Array(25).fill(void 0),n=new Array(5).fill(void 0),r=new Array(3).fill(void 0);return t.createElement("table",{className:e.table},t.createElement("tbody",null,t.createElement("tr",null,t.createElement("td",{className:e.programTitle},t.createElement(E,{width:180,height:40})),a.map((o,l)=>t.createElement("td",{key:l}))),r.map((o,l)=>t.createElement(t.Fragment,{key:l},t.createElement("tr",{className:e.header},t.createElement("td",{className:e.title},t.createElement(E,{width:180})),a.map((c,i)=>t.createElement("td",{key:i}))),n.map((c,i)=>t.createElement("tr",{key:i},t.createElement("td",{className:e.title},t.createElement(E,{width:240})),a.map((d,s)=>t.createElement("td",{key:s,className:e.checkResult},t.createElement(E,{width:18,height:18,variant:"rect"})))))))))},Oe=g(e=>({root:{width:"100%"},table:{overflow:"auto",backgroundColor:e.palette.background.paper,borderCollapse:"collapse",whiteSpace:"nowrap",textAlign:"left","& th, & td":{border:`1px solid ${e.palette.divider}`,borderCollapse:"collapse"},"& th:first-of-type":{position:"sticky",left:0,zIndex:1,maxWidth:"60ch"},"& tfoot":{bottom:0,zIndex:1e3},"& tbody td:first-of-type":{border:`1px solid ${e.palette.divider}`,boxShadow:`1px 0 ${e.palette.background.default}, -1px 0 ${e.palette.background.default}`}}})),Le=b(e=>{const{setError:a,type:n,ownerEntityRef:r}=e,o=Oe(),l=_(B),{data:c,isLoading:i,isError:d}=se(r,{type:n});if(v(()=>{a?.(d?new Error("Error loading program overview"):void 0)},[d,a]),i)return t.createElement(Ae,null);if(!c||!r)return null;const{programs:s,entityRefs:u}=c;if(!i&&!s.length)return t.createElement(M,{missing:"data",title:"Missing programs",description:t.createElement(t.Fragment,null,"Looks like the group"," ",W(x(r),{defaultKind:"Group"})," ","doesn't own any ",n," components that have Soundcheck programs set up.",t.createElement("br",null),t.createElement("br",null),"If you're an administrator, you can learn more about configuring and filtering programs in the"," ",t.createElement(P,{to:"https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend#entity-filter"},"docs"),".")});if(d)return null;const p=u.length>=25?u:[...u,...Array.from({length:25-u.length},()=>{})];return t.createElement("div",{className:o.root},t.createElement("table",{className:o.table,"aria-label":"Check results"},s.map(({program:m,levels:y,highestLevels:w},S)=>t.createElement(Pe,{key:S,program:m,entityRefs:p,levels:y,highestLevels:w,entityRoute:l})),t.createElement(Te,{entityRefs:p})))}),_e=g(e=>({tableContainer:{padding:e.spacing(3),backgroundColor:"transparent"},scrollContainer:{overflow:"scroll",height:`calc(100vh - 86px - 68px - ${e.spacing(3)*2}px)`}})),Ge=()=>{var e;const[a,n]=de(),{group:r,type:o}=a,[l,c]=C(void 0),[i,d]=C(void 0),{data:s,isLoading:u}=le(r),p=l||i,m=f=>{f&&n({...a,group:f})},y=f=>{f&&n({...a,type:f})},w=_e(),S=N(()=>{var f;return((f=s?.types)==null?void 0:f.length)<1?t.createElement(Z,{ownerEntityRef:r}):p?t.createElement(ee,{severity:"error",title:p.message}):t.createElement(Le,{ownerEntityRef:r,type:o,setError:d})},[(e=s?.types)==null?void 0:e.length,p,r,o]);return t.createElement(z,{themeId:"website"},t.createElement(K,{backend:"soundcheck",invalidLicenseMessage:"Submitted check results will still be stored, but it will not be possible to view certifications or individual results until a valid license is present."}),t.createElement(H,{title:"Soundcheck"},t.createElement(me,{onChange:m,initialValue:r,setError:c})),t.createElement(D,{noPadding:!0,stretch:!0},t.createElement(T,{container:!0,spacing:0},t.createElement(T,{item:!0,xs:12},u?t.createElement(ge,null):t.createElement(Ee,{facets:s,type:o,onChange:y})),t.createElement(T,{item:!0,xs:12,className:w.tableContainer},t.createElement("div",{className:w.scrollContainer},S)))))},Fe=()=>t.createElement(Y,null,t.createElement(Ge,null));export{Fe as OverviewPage};
|
|
2
|
+
//# sourceMappingURL=index-5f424c4c.esm.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e,{Fragment as f}from"react";import{A as g,a as E,S as y}from"./SoundcheckQueryClientProvider-68e193be.esm.js";import v from"@material-ui/core/styles/makeStyles";import{Divider as k}from"@material-ui/core";import{useRouteRef as C}from"@backstage/core-plugin-api";import{Link as S,InfoCard as b}from"@backstage/core-components";import{useEntity as h}from"@backstage/plugin-catalog-react";import{SpotifyLicenseBanner as w}from"@spotify/backstage-plugin-core";import{c as R}from"./index-a963da8e.esm.js";import{b as m,a as L,c as D}from"./CertificationSidebar-674faa0f.esm.js";import"@backstage/catalog-model";import"@tanstack/react-query";import"react-router-dom";import"@material-ui/lab";import"@material-ui/icons/Check";import"@material-ui/icons/Close";import"@material-ui/icons/RemoveCircleOutline";import"@material-ui/icons/HelpOutline";import"classnames";import"lodash";import"graphql-request";import"graphql-tag";import"@material-ui/icons/Schedule";import"luxon";import"react-use/lib/useInterval";const I=()=>e.createElement(e.Fragment,null,e.createElement(m,{hideDescription:!0}),e.createElement(m,{hideDescription:!0})),s=v(t=>({certificationWrapper:{display:"flex",justifyContent:"space-between",alignItems:"center"},infoCard:{display:"grid",gridRowGap:t.spacing(2)},emptyState:{"& > div":{backgroundColor:t.palette.background.paper}}})),a=({children:t})=>{const i=s();return e.createElement(b,{title:"Soundcheck"},e.createElement("div",{className:i.infoCard},e.createElement(w,{inline:!0,backend:"soundcheck",invalidLicenseMessage:"Submitted check results will still be stored, but it will not be possible to view certifications or individual results until a valid license is present."}),t))},N=()=>{const{entity:t}=h(),i=s(),{data:n,isError:c,isLoading:p}=L(t),u=C(R);return c?e.createElement(a,null,e.createElement(g,{severity:"error",title:"Error loading certifications"})):p||!n?e.createElement(a,null,e.createElement(I,null)):n.length===0?e.createElement(a,null,e.createElement(E,{className:i.emptyState})):e.createElement(a,null,n.map((r,d)=>{var o,l;return e.createElement(f,{key:r.program.name},e.createElement("div",{className:i.certificationWrapper,"data-testid":"soundcheck-program-row"},e.createElement(D,{key:r.program.id,name:r.program.name,badge:(o=r.highestLevel)==null?void 0:o.badge,level:(l=r.highestLevel)==null?void 0:l.name}),e.createElement(S,{to:u({programId:r.program.id})},"View Details")),d<n.length-1?e.createElement(k,null):null)}))},P=()=>e.createElement(y,null,e.createElement(N,null));export{P as EntitySoundcheckCard};
|
|
2
|
+
//# sourceMappingURL=index-7e30ab1c.esm.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import{createApiRef as O,createRouteRef as y,createSubRouteRef as R,createPlugin as D,createApiFactory as E,discoveryApiRef as S,fetchApiRef as $,createRoutableExtension as k,createComponentExtension as A}from"@backstage/core-plugin-api";import{GraphQLClient as _}from"graphql-request";import o from"graphql-tag";var w=(e=>(e.Failed="FAILED",e.NotApplicable="NOT_APPLICABLE",e.NotReported="NOT_REPORTED",e.Passed="PASSED",e))(w||{});const P=o`
|
|
2
|
+
fragment CertificationSummary on Certification {
|
|
3
|
+
entityRef
|
|
4
|
+
program {
|
|
5
|
+
id
|
|
6
|
+
name
|
|
7
|
+
}
|
|
8
|
+
highestLevel {
|
|
9
|
+
ordinal
|
|
10
|
+
name
|
|
11
|
+
description
|
|
12
|
+
badge {
|
|
13
|
+
... on BadgeVariantMedal {
|
|
14
|
+
variant
|
|
15
|
+
options {
|
|
16
|
+
level
|
|
17
|
+
color
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
`,L=o`
|
|
24
|
+
fragment CheckResultSummary on CheckResult {
|
|
25
|
+
id
|
|
26
|
+
name
|
|
27
|
+
result
|
|
28
|
+
timestamp
|
|
29
|
+
}
|
|
30
|
+
`,q=o`
|
|
31
|
+
fragment CheckResultDetails on CheckResult {
|
|
32
|
+
id
|
|
33
|
+
name
|
|
34
|
+
description
|
|
35
|
+
result
|
|
36
|
+
timestamp
|
|
37
|
+
notes
|
|
38
|
+
}
|
|
39
|
+
`,I=o`
|
|
40
|
+
fragment OverviewLevelResult on OverviewLevelResult {
|
|
41
|
+
ordinal
|
|
42
|
+
name
|
|
43
|
+
checks {
|
|
44
|
+
check {
|
|
45
|
+
id
|
|
46
|
+
name
|
|
47
|
+
}
|
|
48
|
+
results {
|
|
49
|
+
id
|
|
50
|
+
entityRef
|
|
51
|
+
result
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`,M=o`
|
|
56
|
+
fragment HighestLevel on LevelResult {
|
|
57
|
+
entityRef
|
|
58
|
+
ordinal
|
|
59
|
+
name
|
|
60
|
+
badge {
|
|
61
|
+
... on BadgeVariantMedal {
|
|
62
|
+
variant
|
|
63
|
+
options {
|
|
64
|
+
color
|
|
65
|
+
level
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
`,G=o`
|
|
71
|
+
query getAllCertifications($entityRef: String!) {
|
|
72
|
+
certifications(entityRef: $entityRef, includeFilteredChecks: false) {
|
|
73
|
+
...CertificationSummary
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
${P}`,B=o`
|
|
77
|
+
query getCertificationDetails($entityRef: String!, $programId: String!) {
|
|
78
|
+
programCertification(
|
|
79
|
+
entityRef: $entityRef
|
|
80
|
+
programId: $programId
|
|
81
|
+
includeFilteredChecks: false
|
|
82
|
+
) {
|
|
83
|
+
program {
|
|
84
|
+
id
|
|
85
|
+
name
|
|
86
|
+
description
|
|
87
|
+
documentationUrl
|
|
88
|
+
}
|
|
89
|
+
highestLevel {
|
|
90
|
+
name
|
|
91
|
+
ordinal
|
|
92
|
+
badge {
|
|
93
|
+
... on BadgeVariantMedal {
|
|
94
|
+
variant
|
|
95
|
+
options {
|
|
96
|
+
level
|
|
97
|
+
color
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
levels {
|
|
103
|
+
ordinal
|
|
104
|
+
name
|
|
105
|
+
description
|
|
106
|
+
badge {
|
|
107
|
+
... on BadgeVariantMedal {
|
|
108
|
+
variant
|
|
109
|
+
options {
|
|
110
|
+
level
|
|
111
|
+
color
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
certified
|
|
116
|
+
checks {
|
|
117
|
+
...CheckResultSummary
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
${L}`,T=o`
|
|
123
|
+
query getCheckResultDetails($entityRef: String!, $programId: String!, $checkId: String!) {
|
|
124
|
+
checkResult(entityRef: $entityRef, programId: $programId, checkId: $checkId) {
|
|
125
|
+
...CheckResultDetails
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
${q}`,N=o`
|
|
129
|
+
query getFacetsForOwner($ownerEntityRef: String!) {
|
|
130
|
+
facetsForOwner(ownerEntityRef: $ownerEntityRef) {
|
|
131
|
+
types {
|
|
132
|
+
value
|
|
133
|
+
count
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
`,x=o`
|
|
138
|
+
query getProgramOverviewForOwner($ownerEntityRef: String!, $facet: FacetInput) {
|
|
139
|
+
programOverviewForOwner(
|
|
140
|
+
ownerEntityRef: $ownerEntityRef
|
|
141
|
+
facet: $facet
|
|
142
|
+
includeFilteredChecks: false
|
|
143
|
+
) {
|
|
144
|
+
programs {
|
|
145
|
+
program {
|
|
146
|
+
id
|
|
147
|
+
name
|
|
148
|
+
}
|
|
149
|
+
highestLevels {
|
|
150
|
+
...HighestLevel
|
|
151
|
+
}
|
|
152
|
+
levels {
|
|
153
|
+
...OverviewLevelResult
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
entityRefs
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
${M}
|
|
160
|
+
${I}`,V=(e,r,t)=>e();function H(e,r=V){return{getAllCertifications(t,n){return r(i=>e.request(G,t,{...n,...i}),"getAllCertifications","query")},getCertificationDetails(t,n){return r(i=>e.request(B,t,{...n,...i}),"getCertificationDetails","query")},getCheckResultDetails(t,n){return r(i=>e.request(T,t,{...n,...i}),"getCheckResultDetails","query")},getFacetsForOwner(t,n){return r(i=>e.request(N,t,{...n,...i}),"getFacetsForOwner","query")},getProgramOverviewForOwner(t,n){return r(i=>e.request(x,t,{...n,...i}),"getProgramOverviewForOwner","query")}}}var C=(e,r,t)=>{if(!r.has(e))throw TypeError("Cannot "+t)},a=(e,r,t)=>(C(e,r,"read from private field"),t?t.call(e):r.get(e)),l=(e,r,t)=>{if(r.has(e))throw TypeError("Cannot add the same private member more than once");r instanceof WeakSet?r.add(e):r.set(e,t)},m=(e,r,t,n)=>(C(e,r,"write to private field"),n?n.call(e,t):r.set(e,t),t),d,h,c,p,s;const F=O({id:"plugin.soundcheck"}),Q=e=>"response"in e&&"errors"in e.response,W=e=>"response"in e&&"message"in e.response;class U{constructor(r){l(this,d,void 0),l(this,h,void 0),l(this,c,void 0),l(this,p,n=>{var i,u,v;return Q(n)?(v=(u=(i=n.response)==null?void 0:i.errors)==null?void 0:u[0].message)!=null?v:`${n}`:W(n)?n.response.message:`${n}`}),l(this,s,async n=>{try{return await n()}catch(i){throw new Error(`Error from Soundcheck backend: ${a(this,p).call(this,i)}`)}}),m(this,d,r.fetchApi),m(this,h,r.discoveryApi);const t=new _("/graphql",{fetch:async(n,i)=>{const u=`${await a(this,h).getBaseUrl("soundcheck")}${n}`;return a(this,d).fetch(u,i)}});m(this,c,H(t))}async getAllCertifications(r){return a(this,s).call(this,async()=>{const{certifications:t}=await a(this,c).getAllCertifications({entityRef:r});return t})}async getCertificationDetails(r,t){return a(this,s).call(this,async()=>{const{programCertification:n}=await a(this,c).getCertificationDetails({entityRef:r,programId:t});return n})}async getCheckResultDetails(r,t,n){return a(this,s).call(this,async()=>{const{checkResult:i}=await a(this,c).getCheckResultDetails({entityRef:r,programId:t,checkId:n});return i})}async getFacetsForOwner(r){return a(this,s).call(this,async()=>{const{facetsForOwner:t}=await a(this,c).getFacetsForOwner({ownerEntityRef:r});return t})}async getProgramOverviewForOwner(r,t){return a(this,s).call(this,async()=>{const{programOverviewForOwner:n}=await a(this,c).getProgramOverviewForOwner({ownerEntityRef:r,facet:t});return n})}}d=new WeakMap,h=new WeakMap,c=new WeakMap,p=new WeakMap,s=new WeakMap;const g=y({id:"soundcheck-entity"}),z=R({id:"soundcheck-entity-certification-program-redirect",parent:g,path:"/:programId"}),b=R({id:"soundcheck-entity-certification",parent:g,path:"/:programId/:checkId"}),J=y({id:"soundcheck-overview"}),f=D({id:"soundcheck",apis:[E({api:F,deps:{discoveryApi:S,fetchApi:$},factory:e=>new U(e)})],routes:{entityContent:g}}),K=f.provide(k({name:"EntitySoundcheckContent",component:()=>import("./EntitySoundcheckContent-ac9f00dd.esm.js").then(e=>e.EntitySoundcheckContent),mountPoint:g})),X=f.provide(A({name:"EntitySoundcheckCard",component:{lazy:()=>import("./index-7e30ab1c.esm.js").then(e=>e.EntitySoundcheckCard)}})),Y=f.provide(k({name:"SoundcheckOverviewPage",component:()=>import("./index-5f424c4c.esm.js").then(e=>e.OverviewPage),mountPoint:J}));export{K as E,w as R,Y as S,b as a,f as b,z as c,X as d,F as s};
|
|
161
|
+
//# sourceMappingURL=index-a963da8e.esm.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
*
|
|
7
|
+
* The Soundcheck plugin for use within Backstage frontends
|
|
8
|
+
*/
|
|
9
|
+
declare const soundcheckPlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
10
|
+
entityContent: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
11
|
+
}, {}, {}>;
|
|
12
|
+
/**
|
|
13
|
+
* @public
|
|
14
|
+
*
|
|
15
|
+
* Provides an EntityPage tab containing Soundcheck content relevant for the
|
|
16
|
+
* current entity. Expected to be used within {@link @backstage/plugin-catalog#EntityLayout}.
|
|
17
|
+
*/
|
|
18
|
+
declare const EntitySoundcheckContent: () => JSX.Element;
|
|
19
|
+
/**
|
|
20
|
+
* @public
|
|
21
|
+
*
|
|
22
|
+
* Provides Card to be used on the EntityPage inside the Catalog Plugin.
|
|
23
|
+
* Must be used within {@link @backstage/plugin-catalog#EntityLayout}.
|
|
24
|
+
*/
|
|
25
|
+
declare const EntitySoundcheckCard: () => JSX.Element;
|
|
26
|
+
/**
|
|
27
|
+
* @public
|
|
28
|
+
*
|
|
29
|
+
* Provides the /soundcheck route on your Backstage instance to get an overview
|
|
30
|
+
* of your Certifications throughout different groups in your Organization.
|
|
31
|
+
*/
|
|
32
|
+
declare const SoundcheckOverviewPage: () => JSX.Element;
|
|
33
|
+
|
|
34
|
+
export { EntitySoundcheckCard, EntitySoundcheckContent, SoundcheckOverviewPage, soundcheckPlugin };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{d as i,E as r,S as a,b as d}from"./esm/index-a963da8e.esm.js";import"@backstage/core-plugin-api";import"graphql-request";import"graphql-tag";export{i as EntitySoundcheckCard,r as EntitySoundcheckContent,a as SoundcheckOverviewPage,d as soundcheckPlugin};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spotify/backstage-plugin-soundcheck",
|
|
3
|
+
"description": "Ensure quality, reliability and alignment of software development with codified checks and guidance.",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
|
+
"homepage": "https://backstage.spotify.com/",
|
|
7
|
+
"main": "dist/index.esm.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"main": "dist/index.esm.js",
|
|
11
|
+
"types": "dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"backstage": {
|
|
14
|
+
"role": "frontend-plugin"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "backstage-cli package start",
|
|
18
|
+
"build": "backstage-cli package build --minify",
|
|
19
|
+
"lint": "backstage-cli package lint",
|
|
20
|
+
"test": "backstage-cli package test",
|
|
21
|
+
"diff": "backstage-cli plugin:diff",
|
|
22
|
+
"clean": "backstage-cli package clean",
|
|
23
|
+
"prepack": "backstage-cli package prepack",
|
|
24
|
+
"postpack": "backstage-cli package postpack",
|
|
25
|
+
"generate": "graphql-codegen --config codegen.yml",
|
|
26
|
+
"prepare": "yarn generate"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@backstage/catalog-model": "^1.1.4",
|
|
30
|
+
"@backstage/core-components": "^0.12.1",
|
|
31
|
+
"@backstage/core-plugin-api": "^1.2.0",
|
|
32
|
+
"@backstage/plugin-catalog-react": "^1.2.2",
|
|
33
|
+
"@backstage/theme": "^0.2.16",
|
|
34
|
+
"@backstage/types": "^1.0.2",
|
|
35
|
+
"@material-ui/core": "^4.9.13",
|
|
36
|
+
"@material-ui/icons": "^4.9.1",
|
|
37
|
+
"@material-ui/lab": "4.0.0-alpha.61",
|
|
38
|
+
"@spotify/backstage-plugin-core": "^0.2.2",
|
|
39
|
+
"@tanstack/react-query": "^4.6.1",
|
|
40
|
+
"classnames": "^2.3.2",
|
|
41
|
+
"graphql-request": "^5.0.0",
|
|
42
|
+
"lodash": "^4.17.21",
|
|
43
|
+
"luxon": "^3.1.1",
|
|
44
|
+
"react-confetti": "^6.1.0",
|
|
45
|
+
"react-use": "^17.2.4"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": "^16.13.1 || ^17.0.0",
|
|
49
|
+
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@backstage/cli": "^0.22.0",
|
|
53
|
+
"@backstage/core-app-api": "^1.3.0",
|
|
54
|
+
"@backstage/dev-utils": "^1.0.9",
|
|
55
|
+
"@backstage/test-utils": "^1.2.3",
|
|
56
|
+
"@graphql-codegen/cli": "^2.13.1",
|
|
57
|
+
"@graphql-codegen/typescript": "^2.7.3",
|
|
58
|
+
"@graphql-codegen/typescript-graphql-request": "^4.5.5",
|
|
59
|
+
"@graphql-codegen/typescript-operations": "^2.5.3",
|
|
60
|
+
"@spotify/backstage-plugin-core-common": "^0.2.1",
|
|
61
|
+
"@spotify/backstage-plugin-soundcheck-common": "^0.1.1",
|
|
62
|
+
"@testing-library/jest-dom": "^5.10.1",
|
|
63
|
+
"@testing-library/react": "^12.1.3",
|
|
64
|
+
"@testing-library/react-hooks": "^8.0.1",
|
|
65
|
+
"@testing-library/user-event": "^14.0.0",
|
|
66
|
+
"@types/jest": "*",
|
|
67
|
+
"@types/luxon": "^3.0.1",
|
|
68
|
+
"@types/node": "*",
|
|
69
|
+
"cross-fetch": "^3.1.5",
|
|
70
|
+
"msw": "^0.49.0"
|
|
71
|
+
},
|
|
72
|
+
"files": [
|
|
73
|
+
"dist",
|
|
74
|
+
"!dist/**/*.map"
|
|
75
|
+
]
|
|
76
|
+
}
|