@backstage-community/plugin-entity-feedback 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +821 -0
- package/README.md +140 -0
- package/dist/esm/FeedbackRatingsTable-O2SrWgAE.esm.js +99 -0
- package/dist/esm/FeedbackRatingsTable-O2SrWgAE.esm.js.map +1 -0
- package/dist/esm/FeedbackResponseDialog-FxuGsr7u.esm.js +112 -0
- package/dist/esm/FeedbackResponseDialog-FxuGsr7u.esm.js.map +1 -0
- package/dist/esm/LikeDislikeButtons-D7M8hiAk.esm.js +109 -0
- package/dist/esm/LikeDislikeButtons-D7M8hiAk.esm.js.map +1 -0
- package/dist/esm/StarredRatingButtons-7kGQPIqk.esm.js +112 -0
- package/dist/esm/StarredRatingButtons-7kGQPIqk.esm.js.map +1 -0
- package/dist/esm/index-6WuZOQ2W.esm.js +51 -0
- package/dist/esm/index-6WuZOQ2W.esm.js.map +1 -0
- package/dist/esm/index-BNjpfNYP.esm.js +30 -0
- package/dist/esm/index-BNjpfNYP.esm.js.map +1 -0
- package/dist/esm/index-CV29yHQA.esm.js +33 -0
- package/dist/esm/index-CV29yHQA.esm.js.map +1 -0
- package/dist/esm/index-DKWcikxI.esm.js +25 -0
- package/dist/esm/index-DKWcikxI.esm.js.map +1 -0
- package/dist/esm/index-iIL-rmvF.esm.js +46 -0
- package/dist/esm/index-iIL-rmvF.esm.js.map +1 -0
- package/dist/esm/index-xB2VxWp7.esm.js +83 -0
- package/dist/esm/index-xB2VxWp7.esm.js.map +1 -0
- package/dist/index.d.ts +152 -0
- package/dist/index.esm.js +257 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Entity Feedback Plugin
|
|
2
|
+
|
|
3
|
+
Welcome to the entity-feedback plugin!
|
|
4
|
+
|
|
5
|
+
This plugin allows you give and view feedback on entities available in the Backstage catalog.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
### Rate entities
|
|
10
|
+
|
|
11
|
+
#### Like/Dislike rating
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
#### Starred rating
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
### Request additional feedback when poorly rated
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
### View entity feedback responses
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
### View aggregated ratings on owned entities
|
|
28
|
+
|
|
29
|
+
#### Total likes/dislikes
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
#### Star breakdowns
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
The following sections will help you get the Entity Feedback plugin setup and running.
|
|
40
|
+
|
|
41
|
+
Note: this plugin requires authentication and identity configured so Backstage can identify
|
|
42
|
+
which user has rated the entity. If you are using the guest identity provider which comes
|
|
43
|
+
out of the box, this plugin will not work when you test it.
|
|
44
|
+
|
|
45
|
+
### Backend
|
|
46
|
+
|
|
47
|
+
You need to setup the [Entity Feedback backend plugin](https://github.com/backstage/backstage/tree/master/plugins/entity-feedback-backend) before you move forward with any of these steps if you haven't already
|
|
48
|
+
|
|
49
|
+
### Installation
|
|
50
|
+
|
|
51
|
+
Install this plugin:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# From your Backstage root directory
|
|
55
|
+
yarn --cwd packages/app add @backstage-community/plugin-entity-feedback
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Entity Pages
|
|
59
|
+
|
|
60
|
+
Add rating and feedback components to your `EntityPage.tsx` to hook up UI so that users
|
|
61
|
+
can rate your entities and for owners to view feedback/responses.
|
|
62
|
+
|
|
63
|
+
To allow users to apply "Like" and "Dislike" ratings add the following to each kind/type of
|
|
64
|
+
entity in your `EntityPage.tsx` you want to be rated (if you prefer to use star ratings, replace
|
|
65
|
+
`EntityLikeDislikeRatingsCard` with `EntityStarredRatingsCard` and `LikeDislikeButtons` with
|
|
66
|
+
`StarredRatingButtons`):
|
|
67
|
+
|
|
68
|
+
```diff
|
|
69
|
+
import {
|
|
70
|
+
...
|
|
71
|
+
+ InfoCard,
|
|
72
|
+
...
|
|
73
|
+
} from '@backstage/core-components';
|
|
74
|
+
+import {
|
|
75
|
+
+ EntityFeedbackResponseContent,
|
|
76
|
+
+ EntityLikeDislikeRatingsCard,
|
|
77
|
+
+ LikeDislikeButtons,
|
|
78
|
+
+} from '@backstage-community/plugin-entity-feedback';
|
|
79
|
+
|
|
80
|
+
// Add to each applicable kind/type of entity as desired
|
|
81
|
+
const overviewContent = (
|
|
82
|
+
<Grid container spacing={3} alignItems="stretch">
|
|
83
|
+
...
|
|
84
|
+
+ <Grid item md={2}>
|
|
85
|
+
+ <InfoCard title="Rate this entity">
|
|
86
|
+
+ <LikeDislikeButtons />
|
|
87
|
+
+ </InfoCard>
|
|
88
|
+
+ </Grid>
|
|
89
|
+
...
|
|
90
|
+
</Grid>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
// Add to each applicable kind/type of entity as desired
|
|
96
|
+
const serviceEntityPage = (
|
|
97
|
+
<EntityLayoutWrapper>
|
|
98
|
+
...
|
|
99
|
+
+ <EntityLayout.Route path="/feedback" title="Feedback">
|
|
100
|
+
+ <EntityFeedbackResponseContent />
|
|
101
|
+
+ </EntityLayout.Route>
|
|
102
|
+
...
|
|
103
|
+
</EntityLayoutWrapper>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
// Add ratings card component to user/group entities to view ratings of owned entities
|
|
109
|
+
const userPage = (
|
|
110
|
+
<EntityLayoutWrapper>
|
|
111
|
+
<EntityLayout.Route path="/" title="Overview">
|
|
112
|
+
<Grid container spacing={3}>
|
|
113
|
+
...
|
|
114
|
+
+ <Grid item xs={12}>
|
|
115
|
+
+ <EntityLikeDislikeRatingsCard />
|
|
116
|
+
+ </Grid>
|
|
117
|
+
...
|
|
118
|
+
</Grid>
|
|
119
|
+
</Grid>
|
|
120
|
+
</EntityLayout.Route>
|
|
121
|
+
</EntityLayoutWrapper>
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const groupPage = (
|
|
125
|
+
<EntityLayoutWrapper>
|
|
126
|
+
<EntityLayout.Route path="/" title="Overview">
|
|
127
|
+
<Grid container spacing={3}>
|
|
128
|
+
...
|
|
129
|
+
+ <Grid item xs={12}>
|
|
130
|
+
+ <EntityLikeDislikeRatingsCard />
|
|
131
|
+
+ </Grid>
|
|
132
|
+
...
|
|
133
|
+
</Grid>
|
|
134
|
+
</Grid>
|
|
135
|
+
</EntityLayout.Route>
|
|
136
|
+
</EntityLayoutWrapper>
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Note: For a full example of this you can look at [this EntityPage](../../packages/app/src/components/catalog/EntityPage.tsx).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { parseEntityRef } from '@backstage/catalog-model';
|
|
2
|
+
import { SubvalueCell, ErrorPanel, Table } from '@backstage/core-components';
|
|
3
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
4
|
+
import { EntityRefLink } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
7
|
+
import '@backstage/errors';
|
|
8
|
+
import { entityFeedbackApiRef } from '../index.esm.js';
|
|
9
|
+
|
|
10
|
+
const FeedbackRatingsTable = (props) => {
|
|
11
|
+
const {
|
|
12
|
+
allEntities,
|
|
13
|
+
ownerRef,
|
|
14
|
+
ratingValues,
|
|
15
|
+
title = "Entity Ratings"
|
|
16
|
+
} = props;
|
|
17
|
+
const feedbackApi = useApi(entityFeedbackApiRef);
|
|
18
|
+
const {
|
|
19
|
+
error,
|
|
20
|
+
loading,
|
|
21
|
+
value: ratings
|
|
22
|
+
} = useAsync(async () => {
|
|
23
|
+
if (allEntities) {
|
|
24
|
+
return feedbackApi.getAllRatings();
|
|
25
|
+
}
|
|
26
|
+
if (!ownerRef) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return feedbackApi.getOwnedRatings(ownerRef);
|
|
30
|
+
}, [allEntities, feedbackApi, ownerRef]);
|
|
31
|
+
const columns = [
|
|
32
|
+
{ title: "Title", field: "entityTitle", hidden: true, searchable: true },
|
|
33
|
+
{
|
|
34
|
+
title: "Entity",
|
|
35
|
+
field: "entityRef",
|
|
36
|
+
highlight: true,
|
|
37
|
+
customSort: (a, b) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
const titleA = (_a = a.entityTitle) != null ? _a : parseEntityRef(a.entityRef).name;
|
|
40
|
+
const titleB = (_b = b.entityTitle) != null ? _b : parseEntityRef(b.entityRef).name;
|
|
41
|
+
return titleA.localeCompare(titleB);
|
|
42
|
+
},
|
|
43
|
+
render: (rating) => {
|
|
44
|
+
const compoundRef = parseEntityRef(rating.entityRef);
|
|
45
|
+
return /* @__PURE__ */ React.createElement(
|
|
46
|
+
SubvalueCell,
|
|
47
|
+
{
|
|
48
|
+
value: /* @__PURE__ */ React.createElement(
|
|
49
|
+
EntityRefLink,
|
|
50
|
+
{
|
|
51
|
+
entityRef: rating.entityRef,
|
|
52
|
+
defaultKind: compoundRef.kind,
|
|
53
|
+
title: rating.entityTitle
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
subvalue: compoundRef.kind
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
...ratingValues.map((ratingVal) => ({
|
|
62
|
+
title: ratingVal,
|
|
63
|
+
field: `ratings.${ratingVal}`
|
|
64
|
+
}))
|
|
65
|
+
];
|
|
66
|
+
const ratingsRows = ratings == null ? void 0 : ratings.filter(
|
|
67
|
+
(r) => Object.keys(r.ratings).some((v) => ratingValues.includes(v))
|
|
68
|
+
);
|
|
69
|
+
if (error) {
|
|
70
|
+
return /* @__PURE__ */ React.createElement(
|
|
71
|
+
ErrorPanel,
|
|
72
|
+
{
|
|
73
|
+
defaultExpanded: true,
|
|
74
|
+
title: "Failed to load feedback ratings",
|
|
75
|
+
error
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ React.createElement(
|
|
80
|
+
Table,
|
|
81
|
+
{
|
|
82
|
+
columns,
|
|
83
|
+
data: ratingsRows != null ? ratingsRows : [],
|
|
84
|
+
isLoading: loading,
|
|
85
|
+
options: {
|
|
86
|
+
emptyRowsWhenPaging: false,
|
|
87
|
+
loadingType: "linear",
|
|
88
|
+
pageSize: 20,
|
|
89
|
+
pageSizeOptions: [20, 50, 100],
|
|
90
|
+
paging: true,
|
|
91
|
+
showEmptyDataSourceMessage: !loading
|
|
92
|
+
},
|
|
93
|
+
title
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export { FeedbackRatingsTable as F };
|
|
99
|
+
//# sourceMappingURL=FeedbackRatingsTable-O2SrWgAE.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedbackRatingsTable-O2SrWgAE.esm.js","sources":["../../src/components/FeedbackRatingsTable/FeedbackRatingsTable.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport { ErrorPanel, SubvalueCell, Table } from '@backstage/core-components';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { EntityRefLink } from '@backstage/plugin-catalog-react';\nimport { EntityRatingsData } from '@backstage-community/plugin-entity-feedback-common';\nimport React from 'react';\nimport useAsync from 'react-use/esm/useAsync';\n\nimport { entityFeedbackApiRef } from '../../api';\n\ninterface FeedbackRatingsTableProps {\n allEntities?: boolean;\n ownerRef?: string;\n ratingValues: string[];\n title?: string;\n}\n\nexport const FeedbackRatingsTable = (props: FeedbackRatingsTableProps) => {\n const {\n allEntities,\n ownerRef,\n ratingValues,\n title = 'Entity Ratings',\n } = props;\n const feedbackApi = useApi(entityFeedbackApiRef);\n\n const {\n error,\n loading,\n value: ratings,\n } = useAsync(async () => {\n if (allEntities) {\n return feedbackApi.getAllRatings();\n }\n\n if (!ownerRef) {\n return [];\n }\n\n return feedbackApi.getOwnedRatings(ownerRef);\n }, [allEntities, feedbackApi, ownerRef]);\n\n const columns = [\n { title: 'Title', field: 'entityTitle', hidden: true, searchable: true },\n {\n title: 'Entity',\n field: 'entityRef',\n highlight: true,\n customSort: (a: EntityRatingsData, b: EntityRatingsData) => {\n const titleA = a.entityTitle ?? parseEntityRef(a.entityRef).name;\n const titleB = b.entityTitle ?? parseEntityRef(b.entityRef).name;\n return titleA.localeCompare(titleB);\n },\n render: (rating: EntityRatingsData) => {\n const compoundRef = parseEntityRef(rating.entityRef);\n return (\n <SubvalueCell\n value={\n <EntityRefLink\n entityRef={rating.entityRef}\n defaultKind={compoundRef.kind}\n title={rating.entityTitle}\n />\n }\n subvalue={compoundRef.kind}\n />\n );\n },\n },\n ...ratingValues.map(ratingVal => ({\n title: ratingVal,\n field: `ratings.${ratingVal}`,\n })),\n ];\n\n // Exclude entities that don't have applicable ratings\n const ratingsRows = ratings?.filter(r =>\n Object.keys(r.ratings).some(v => ratingValues.includes(v)),\n );\n\n if (error) {\n return (\n <ErrorPanel\n defaultExpanded\n title=\"Failed to load feedback ratings\"\n error={error}\n />\n );\n }\n\n return (\n <Table<EntityRatingsData>\n columns={columns}\n data={ratingsRows ?? []}\n isLoading={loading}\n options={{\n emptyRowsWhenPaging: false,\n loadingType: 'linear',\n pageSize: 20,\n pageSizeOptions: [20, 50, 100],\n paging: true,\n showEmptyDataSourceMessage: !loading,\n }}\n title={title}\n />\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAiCa,MAAA,oBAAA,GAAuB,CAAC,KAAqC,KAAA;AACxE,EAAM,MAAA;AAAA,IACJ,WAAA;AAAA,IACA,QAAA;AAAA,IACA,YAAA;AAAA,IACA,KAAQ,GAAA,gBAAA;AAAA,GACN,GAAA,KAAA,CAAA;AACJ,EAAM,MAAA,WAAA,GAAc,OAAO,oBAAoB,CAAA,CAAA;AAE/C,EAAM,MAAA;AAAA,IACJ,KAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAO,EAAA,OAAA;AAAA,GACT,GAAI,SAAS,YAAY;AACvB,IAAA,IAAI,WAAa,EAAA;AACf,MAAA,OAAO,YAAY,aAAc,EAAA,CAAA;AAAA,KACnC;AAEA,IAAA,IAAI,CAAC,QAAU,EAAA;AACb,MAAA,OAAO,EAAC,CAAA;AAAA,KACV;AAEA,IAAO,OAAA,WAAA,CAAY,gBAAgB,QAAQ,CAAA,CAAA;AAAA,GAC1C,EAAA,CAAC,WAAa,EAAA,WAAA,EAAa,QAAQ,CAAC,CAAA,CAAA;AAEvC,EAAA,MAAM,OAAU,GAAA;AAAA,IACd,EAAE,OAAO,OAAS,EAAA,KAAA,EAAO,eAAe,MAAQ,EAAA,IAAA,EAAM,YAAY,IAAK,EAAA;AAAA,IACvE;AAAA,MACE,KAAO,EAAA,QAAA;AAAA,MACP,KAAO,EAAA,WAAA;AAAA,MACP,SAAW,EAAA,IAAA;AAAA,MACX,UAAA,EAAY,CAAC,CAAA,EAAsB,CAAyB,KAAA;AAhElE,QAAA,IAAA,EAAA,EAAA,EAAA,CAAA;AAiEQ,QAAA,MAAM,UAAS,EAAE,GAAA,CAAA,CAAA,WAAA,KAAF,YAAiB,cAAe,CAAA,CAAA,CAAE,SAAS,CAAE,CAAA,IAAA,CAAA;AAC5D,QAAA,MAAM,UAAS,EAAE,GAAA,CAAA,CAAA,WAAA,KAAF,YAAiB,cAAe,CAAA,CAAA,CAAE,SAAS,CAAE,CAAA,IAAA,CAAA;AAC5D,QAAO,OAAA,MAAA,CAAO,cAAc,MAAM,CAAA,CAAA;AAAA,OACpC;AAAA,MACA,MAAA,EAAQ,CAAC,MAA8B,KAAA;AACrC,QAAM,MAAA,WAAA,GAAc,cAAe,CAAA,MAAA,CAAO,SAAS,CAAA,CAAA;AACnD,QACE,uBAAA,KAAA,CAAA,aAAA;AAAA,UAAC,YAAA;AAAA,UAAA;AAAA,YACC,KACE,kBAAA,KAAA,CAAA,aAAA;AAAA,cAAC,aAAA;AAAA,cAAA;AAAA,gBACC,WAAW,MAAO,CAAA,SAAA;AAAA,gBAClB,aAAa,WAAY,CAAA,IAAA;AAAA,gBACzB,OAAO,MAAO,CAAA,WAAA;AAAA,eAAA;AAAA,aAChB;AAAA,YAEF,UAAU,WAAY,CAAA,IAAA;AAAA,WAAA;AAAA,SACxB,CAAA;AAAA,OAEJ;AAAA,KACF;AAAA,IACA,GAAG,YAAa,CAAA,GAAA,CAAI,CAAc,SAAA,MAAA;AAAA,MAChC,KAAO,EAAA,SAAA;AAAA,MACP,KAAA,EAAO,WAAW,SAAS,CAAA,CAAA;AAAA,KAC3B,CAAA,CAAA;AAAA,GACJ,CAAA;AAGA,EAAA,MAAM,cAAc,OAAS,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,OAAA,CAAA,MAAA;AAAA,IAAO,CAAA,CAAA,KAClC,MAAO,CAAA,IAAA,CAAK,CAAE,CAAA,OAAO,CAAE,CAAA,IAAA,CAAK,CAAK,CAAA,KAAA,YAAA,CAAa,QAAS,CAAA,CAAC,CAAC,CAAA;AAAA,GAAA,CAAA;AAG3D,EAAA,IAAI,KAAO,EAAA;AACT,IACE,uBAAA,KAAA,CAAA,aAAA;AAAA,MAAC,UAAA;AAAA,MAAA;AAAA,QACC,eAAe,EAAA,IAAA;AAAA,QACf,KAAM,EAAA,iCAAA;AAAA,QACN,KAAA;AAAA,OAAA;AAAA,KACF,CAAA;AAAA,GAEJ;AAEA,EACE,uBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,OAAA;AAAA,MACA,IAAA,EAAM,oCAAe,EAAC;AAAA,MACtB,SAAW,EAAA,OAAA;AAAA,MACX,OAAS,EAAA;AAAA,QACP,mBAAqB,EAAA,KAAA;AAAA,QACrB,WAAa,EAAA,QAAA;AAAA,QACb,QAAU,EAAA,EAAA;AAAA,QACV,eAAiB,EAAA,CAAC,EAAI,EAAA,EAAA,EAAI,GAAG,CAAA;AAAA,QAC7B,MAAQ,EAAA,IAAA;AAAA,QACR,4BAA4B,CAAC,OAAA;AAAA,OAC/B;AAAA,MACA,KAAA;AAAA,KAAA;AAAA,GACF,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
2
|
+
import { Progress } from '@backstage/core-components';
|
|
3
|
+
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import Button from '@material-ui/core/Button';
|
|
5
|
+
import Checkbox from '@material-ui/core/Checkbox';
|
|
6
|
+
import Dialog from '@material-ui/core/Dialog';
|
|
7
|
+
import DialogActions from '@material-ui/core/DialogActions';
|
|
8
|
+
import DialogContent from '@material-ui/core/DialogContent';
|
|
9
|
+
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
10
|
+
import FormControl from '@material-ui/core/FormControl';
|
|
11
|
+
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
|
12
|
+
import FormGroup from '@material-ui/core/FormGroup';
|
|
13
|
+
import FormLabel from '@material-ui/core/FormLabel';
|
|
14
|
+
import Grid from '@material-ui/core/Grid';
|
|
15
|
+
import Switch from '@material-ui/core/Switch';
|
|
16
|
+
import TextField from '@material-ui/core/TextField';
|
|
17
|
+
import Typography from '@material-ui/core/Typography';
|
|
18
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
19
|
+
import React, { useState } from 'react';
|
|
20
|
+
import useAsyncFn from 'react-use/esm/useAsyncFn';
|
|
21
|
+
import '@backstage/errors';
|
|
22
|
+
import { entityFeedbackApiRef } from '../index.esm.js';
|
|
23
|
+
|
|
24
|
+
const defaultFeedbackResponses = [
|
|
25
|
+
{ id: "incorrect", label: "Incorrect info" },
|
|
26
|
+
{ id: "missing", label: "Missing info" },
|
|
27
|
+
{ id: "other", label: "Other (please specify below)" }
|
|
28
|
+
];
|
|
29
|
+
const useStyles = makeStyles({
|
|
30
|
+
contactConsent: {
|
|
31
|
+
marginTop: "5px"
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const FeedbackResponseDialog = (props) => {
|
|
35
|
+
const {
|
|
36
|
+
entity,
|
|
37
|
+
feedbackDialogResponses = defaultFeedbackResponses,
|
|
38
|
+
feedbackDialogTitle = "Please provide feedback on what can be improved",
|
|
39
|
+
open,
|
|
40
|
+
onClose
|
|
41
|
+
} = props;
|
|
42
|
+
const classes = useStyles();
|
|
43
|
+
const errorApi = useApi(errorApiRef);
|
|
44
|
+
const feedbackApi = useApi(entityFeedbackApiRef);
|
|
45
|
+
const [responseSelections, setResponseSelections] = useState(
|
|
46
|
+
Object.fromEntries(feedbackDialogResponses.map((r) => [r.id, false]))
|
|
47
|
+
);
|
|
48
|
+
const [comments, setComments] = useState("");
|
|
49
|
+
const [consent, setConsent] = useState(true);
|
|
50
|
+
const [{ loading: saving }, saveResponse] = useAsyncFn(async () => {
|
|
51
|
+
try {
|
|
52
|
+
await feedbackApi.recordResponse(stringifyEntityRef(entity), {
|
|
53
|
+
comments,
|
|
54
|
+
consent,
|
|
55
|
+
response: Object.keys(responseSelections).filter((id) => responseSelections[id]).join(",")
|
|
56
|
+
});
|
|
57
|
+
onClose();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
errorApi.post(e);
|
|
60
|
+
}
|
|
61
|
+
}, [comments, consent, entity, feedbackApi, onClose, responseSelections]);
|
|
62
|
+
return /* @__PURE__ */ React.createElement(Dialog, { open, onClose: () => !saving && onClose() }, saving && /* @__PURE__ */ React.createElement(Progress, null), /* @__PURE__ */ React.createElement(DialogTitle, null, feedbackDialogTitle), /* @__PURE__ */ React.createElement(DialogContent, null, /* @__PURE__ */ React.createElement(FormControl, { component: "fieldset" }, /* @__PURE__ */ React.createElement(FormLabel, { component: "legend" }, "Choose all that apply"), /* @__PURE__ */ React.createElement(FormGroup, null, feedbackDialogResponses.map((response) => /* @__PURE__ */ React.createElement(
|
|
63
|
+
FormControlLabel,
|
|
64
|
+
{
|
|
65
|
+
key: response.id,
|
|
66
|
+
control: /* @__PURE__ */ React.createElement(
|
|
67
|
+
Checkbox,
|
|
68
|
+
{
|
|
69
|
+
checked: responseSelections[response.id],
|
|
70
|
+
disabled: saving,
|
|
71
|
+
name: response.id,
|
|
72
|
+
onChange: (e) => setResponseSelections({
|
|
73
|
+
...responseSelections,
|
|
74
|
+
[e.target.name]: e.target.checked
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
label: response.label
|
|
79
|
+
}
|
|
80
|
+
)))), /* @__PURE__ */ React.createElement(FormControl, { fullWidth: true }, /* @__PURE__ */ React.createElement(
|
|
81
|
+
TextField,
|
|
82
|
+
{
|
|
83
|
+
"data-testid": "feedback-response-dialog-comments-input",
|
|
84
|
+
disabled: saving,
|
|
85
|
+
label: "Additional comments",
|
|
86
|
+
multiline: true,
|
|
87
|
+
minRows: 2,
|
|
88
|
+
onChange: (e) => setComments(e.target.value),
|
|
89
|
+
variant: "outlined",
|
|
90
|
+
value: comments
|
|
91
|
+
}
|
|
92
|
+
)), /* @__PURE__ */ React.createElement(Typography, { className: classes.contactConsent }, "Can we reach out to you for more info?", /* @__PURE__ */ React.createElement(Grid, { component: "label", container: true, alignItems: "center", spacing: 1 }, /* @__PURE__ */ React.createElement(Grid, { item: true }, "No"), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(
|
|
93
|
+
Switch,
|
|
94
|
+
{
|
|
95
|
+
checked: consent,
|
|
96
|
+
disabled: saving,
|
|
97
|
+
onChange: (e) => setConsent(e.target.checked)
|
|
98
|
+
}
|
|
99
|
+
)), /* @__PURE__ */ React.createElement(Grid, { item: true }, "Yes")))), /* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(Button, { color: "primary", disabled: saving, onClick: onClose }, "Close"), /* @__PURE__ */ React.createElement(
|
|
100
|
+
Button,
|
|
101
|
+
{
|
|
102
|
+
color: "primary",
|
|
103
|
+
"data-testid": "feedback-response-dialog-submit-button",
|
|
104
|
+
disabled: saving,
|
|
105
|
+
onClick: saveResponse
|
|
106
|
+
},
|
|
107
|
+
"Submit"
|
|
108
|
+
)));
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { FeedbackResponseDialog as F };
|
|
112
|
+
//# sourceMappingURL=FeedbackResponseDialog-FxuGsr7u.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FeedbackResponseDialog-FxuGsr7u.esm.js","sources":["../../src/components/FeedbackResponseDialog/FeedbackResponseDialog.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity, stringifyEntityRef } from '@backstage/catalog-model';\nimport { Progress } from '@backstage/core-components';\nimport { ErrorApiError, errorApiRef, useApi } from '@backstage/core-plugin-api';\nimport Button from '@material-ui/core/Button';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport FormControl from '@material-ui/core/FormControl';\nimport FormControlLabel from '@material-ui/core/FormControlLabel';\nimport FormGroup from '@material-ui/core/FormGroup';\nimport FormLabel from '@material-ui/core/FormLabel';\nimport Grid from '@material-ui/core/Grid';\nimport Switch from '@material-ui/core/Switch';\nimport TextField from '@material-ui/core/TextField';\nimport Typography from '@material-ui/core/Typography';\nimport { makeStyles } from '@material-ui/core/styles';\nimport React, { ReactNode, useState } from 'react';\nimport useAsyncFn from 'react-use/esm/useAsyncFn';\n\nimport { entityFeedbackApiRef } from '../../api';\n\n/**\n * @public\n */\nexport interface EntityFeedbackResponse {\n id: string;\n label: string;\n}\n\nconst defaultFeedbackResponses: EntityFeedbackResponse[] = [\n { id: 'incorrect', label: 'Incorrect info' },\n { id: 'missing', label: 'Missing info' },\n { id: 'other', label: 'Other (please specify below)' },\n];\n\n/**\n * @public\n */\nexport interface FeedbackResponseDialogProps {\n entity: Entity;\n feedbackDialogResponses?: EntityFeedbackResponse[];\n feedbackDialogTitle?: ReactNode;\n open: boolean;\n onClose: () => void;\n}\n\nconst useStyles = makeStyles({\n contactConsent: {\n marginTop: '5px',\n },\n});\n\nexport const FeedbackResponseDialog = (props: FeedbackResponseDialogProps) => {\n const {\n entity,\n feedbackDialogResponses = defaultFeedbackResponses,\n feedbackDialogTitle = 'Please provide feedback on what can be improved',\n open,\n onClose,\n } = props;\n const classes = useStyles();\n const errorApi = useApi(errorApiRef);\n const feedbackApi = useApi(entityFeedbackApiRef);\n const [responseSelections, setResponseSelections] = useState(\n Object.fromEntries(feedbackDialogResponses.map(r => [r.id, false])),\n );\n const [comments, setComments] = useState('');\n const [consent, setConsent] = useState(true);\n\n const [{ loading: saving }, saveResponse] = useAsyncFn(async () => {\n try {\n await feedbackApi.recordResponse(stringifyEntityRef(entity), {\n comments,\n consent,\n response: Object.keys(responseSelections)\n .filter(id => responseSelections[id])\n .join(','),\n });\n onClose();\n } catch (e) {\n errorApi.post(e as ErrorApiError);\n }\n }, [comments, consent, entity, feedbackApi, onClose, responseSelections]);\n\n return (\n <Dialog open={open} onClose={() => !saving && onClose()}>\n {saving && <Progress />}\n <DialogTitle>{feedbackDialogTitle}</DialogTitle>\n <DialogContent>\n <FormControl component=\"fieldset\">\n <FormLabel component=\"legend\">Choose all that apply</FormLabel>\n <FormGroup>\n {feedbackDialogResponses.map(response => (\n <FormControlLabel\n key={response.id}\n control={\n <Checkbox\n checked={responseSelections[response.id]}\n disabled={saving}\n name={response.id}\n onChange={e =>\n setResponseSelections({\n ...responseSelections,\n [e.target.name]: e.target.checked,\n })\n }\n />\n }\n label={response.label}\n />\n ))}\n </FormGroup>\n </FormControl>\n <FormControl fullWidth>\n <TextField\n data-testid=\"feedback-response-dialog-comments-input\"\n disabled={saving}\n label=\"Additional comments\"\n multiline\n minRows={2}\n onChange={e => setComments(e.target.value)}\n variant=\"outlined\"\n value={comments}\n />\n </FormControl>\n <Typography className={classes.contactConsent}>\n Can we reach out to you for more info?\n <Grid component=\"label\" container alignItems=\"center\" spacing={1}>\n <Grid item>No</Grid>\n <Grid item>\n <Switch\n checked={consent}\n disabled={saving}\n onChange={e => setConsent(e.target.checked)}\n />\n </Grid>\n <Grid item>Yes</Grid>\n </Grid>\n </Typography>\n </DialogContent>\n <DialogActions>\n <Button color=\"primary\" disabled={saving} onClick={onClose}>\n Close\n </Button>\n <Button\n color=\"primary\"\n data-testid=\"feedback-response-dialog-submit-button\"\n disabled={saving}\n onClick={saveResponse}\n >\n Submit\n </Button>\n </DialogActions>\n </Dialog>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
2
|
+
import { Progress } from '@backstage/core-components';
|
|
3
|
+
import { useApi, errorApiRef, identityApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { useAsyncEntity } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import IconButton from '@material-ui/core/IconButton';
|
|
6
|
+
import Tooltip from '@material-ui/core/Tooltip';
|
|
7
|
+
import ThumbDownIcon from '@material-ui/icons/ThumbDown';
|
|
8
|
+
import ThumbUpIcon from '@material-ui/icons/ThumbUp';
|
|
9
|
+
import ThumbDownOutlinedIcon from '@material-ui/icons/ThumbDownOutlined';
|
|
10
|
+
import ThumbUpOutlinedIcon from '@material-ui/icons/ThumbUpOutlined';
|
|
11
|
+
import React, { useState, useCallback } from 'react';
|
|
12
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
13
|
+
import useAsyncFn from 'react-use/esm/useAsyncFn';
|
|
14
|
+
import '@backstage/errors';
|
|
15
|
+
import { entityFeedbackApiRef } from '../index.esm.js';
|
|
16
|
+
import { F as FeedbackResponseDialog } from './FeedbackResponseDialog-FxuGsr7u.esm.js';
|
|
17
|
+
|
|
18
|
+
var FeedbackRatings = /* @__PURE__ */ ((FeedbackRatings2) => {
|
|
19
|
+
FeedbackRatings2["like"] = "LIKE";
|
|
20
|
+
FeedbackRatings2["dislike"] = "DISLIKE";
|
|
21
|
+
FeedbackRatings2["neutral"] = "NEUTRAL";
|
|
22
|
+
return FeedbackRatings2;
|
|
23
|
+
})(FeedbackRatings || {});
|
|
24
|
+
const LikeDislikeButtons = (props) => {
|
|
25
|
+
const {
|
|
26
|
+
feedbackDialogResponses,
|
|
27
|
+
feedbackDialogTitle,
|
|
28
|
+
requestResponse = true
|
|
29
|
+
} = props;
|
|
30
|
+
const errorApi = useApi(errorApiRef);
|
|
31
|
+
const feedbackApi = useApi(entityFeedbackApiRef);
|
|
32
|
+
const identityApi = useApi(identityApiRef);
|
|
33
|
+
const [rating, setRating] = useState(
|
|
34
|
+
"NEUTRAL" /* neutral */
|
|
35
|
+
);
|
|
36
|
+
const [openFeedbackDialog, setOpenFeedbackDialog] = useState(false);
|
|
37
|
+
const { entity, loading: loadingEntity } = useAsyncEntity();
|
|
38
|
+
const { loading: loadingFeedback } = useAsync(async () => {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
if (!entity) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const identity = await identityApi.getBackstageIdentity();
|
|
45
|
+
const prevFeedback = await feedbackApi.getRatings(
|
|
46
|
+
stringifyEntityRef(entity)
|
|
47
|
+
);
|
|
48
|
+
setRating(
|
|
49
|
+
(_b = (_a = prevFeedback.find((r) => r.userRef === identity.userEntityRef)) == null ? void 0 : _a.rating) != null ? _b : rating
|
|
50
|
+
);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
errorApi.post(e);
|
|
53
|
+
}
|
|
54
|
+
}, [entity, feedbackApi, setRating]);
|
|
55
|
+
const [{ loading: savingFeedback }, saveFeedback] = useAsyncFn(
|
|
56
|
+
async (feedback) => {
|
|
57
|
+
try {
|
|
58
|
+
await feedbackApi.recordRating(stringifyEntityRef(entity), feedback);
|
|
59
|
+
setRating(feedback);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
errorApi.post(e);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[entity, feedbackApi, setRating]
|
|
65
|
+
);
|
|
66
|
+
const applyRating = useCallback(
|
|
67
|
+
(feedback) => {
|
|
68
|
+
if (feedback === rating) {
|
|
69
|
+
saveFeedback("NEUTRAL" /* neutral */);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
saveFeedback(feedback);
|
|
73
|
+
if (feedback === "DISLIKE" /* dislike */ && requestResponse) {
|
|
74
|
+
setOpenFeedbackDialog(true);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
[rating, requestResponse, saveFeedback, setOpenFeedbackDialog]
|
|
78
|
+
);
|
|
79
|
+
if (loadingEntity || loadingFeedback || savingFeedback) {
|
|
80
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
81
|
+
}
|
|
82
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
83
|
+
IconButton,
|
|
84
|
+
{
|
|
85
|
+
"data-testid": "entity-feedback-like-button",
|
|
86
|
+
onClick: () => applyRating("LIKE" /* like */)
|
|
87
|
+
},
|
|
88
|
+
rating === "LIKE" /* like */ ? /* @__PURE__ */ React.createElement(Tooltip, { title: "Liked" }, /* @__PURE__ */ React.createElement(ThumbUpIcon, { fontSize: "small" })) : /* @__PURE__ */ React.createElement(Tooltip, { title: "Like" }, /* @__PURE__ */ React.createElement(ThumbUpOutlinedIcon, { fontSize: "small" }))
|
|
89
|
+
), /* @__PURE__ */ React.createElement(
|
|
90
|
+
IconButton,
|
|
91
|
+
{
|
|
92
|
+
"data-testid": "entity-feedback-dislike-button",
|
|
93
|
+
onClick: () => applyRating("DISLIKE" /* dislike */)
|
|
94
|
+
},
|
|
95
|
+
rating === "DISLIKE" /* dislike */ ? /* @__PURE__ */ React.createElement(Tooltip, { title: "Disliked" }, /* @__PURE__ */ React.createElement(ThumbDownIcon, { fontSize: "small" })) : /* @__PURE__ */ React.createElement(Tooltip, { title: "Dislike" }, /* @__PURE__ */ React.createElement(ThumbDownOutlinedIcon, { fontSize: "small" }))
|
|
96
|
+
), /* @__PURE__ */ React.createElement(
|
|
97
|
+
FeedbackResponseDialog,
|
|
98
|
+
{
|
|
99
|
+
entity,
|
|
100
|
+
open: openFeedbackDialog,
|
|
101
|
+
onClose: () => setOpenFeedbackDialog(false),
|
|
102
|
+
feedbackDialogResponses,
|
|
103
|
+
feedbackDialogTitle
|
|
104
|
+
}
|
|
105
|
+
));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { FeedbackRatings as F, LikeDislikeButtons as L };
|
|
109
|
+
//# sourceMappingURL=LikeDislikeButtons-D7M8hiAk.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LikeDislikeButtons-D7M8hiAk.esm.js","sources":["../../src/components/LikeDislikeButtons/LikeDislikeButtons.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { Progress } from '@backstage/core-components';\nimport {\n ErrorApiError,\n errorApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\nimport { useAsyncEntity } from '@backstage/plugin-catalog-react';\nimport IconButton from '@material-ui/core/IconButton';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport ThumbDownIcon from '@material-ui/icons/ThumbDown';\nimport ThumbUpIcon from '@material-ui/icons/ThumbUp';\nimport ThumbDownOutlinedIcon from '@material-ui/icons/ThumbDownOutlined';\nimport ThumbUpOutlinedIcon from '@material-ui/icons/ThumbUpOutlined';\nimport React, { ReactNode, useCallback, useState } from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport useAsyncFn from 'react-use/esm/useAsyncFn';\n\nimport { entityFeedbackApiRef } from '../../api';\nimport {\n EntityFeedbackResponse,\n FeedbackResponseDialog,\n} from '../FeedbackResponseDialog';\n\nexport enum FeedbackRatings {\n like = 'LIKE',\n dislike = 'DISLIKE',\n neutral = 'NEUTRAL',\n}\n\n/**\n * @public\n */\nexport interface LikeDislikeButtonsProps {\n feedbackDialogResponses?: EntityFeedbackResponse[];\n feedbackDialogTitle?: ReactNode;\n requestResponse?: boolean;\n}\n\nexport const LikeDislikeButtons = (props: LikeDislikeButtonsProps) => {\n const {\n feedbackDialogResponses,\n feedbackDialogTitle,\n requestResponse = true,\n } = props;\n const errorApi = useApi(errorApiRef);\n const feedbackApi = useApi(entityFeedbackApiRef);\n const identityApi = useApi(identityApiRef);\n const [rating, setRating] = useState<FeedbackRatings>(\n FeedbackRatings.neutral,\n );\n const [openFeedbackDialog, setOpenFeedbackDialog] = useState(false);\n const { entity, loading: loadingEntity } = useAsyncEntity();\n\n const { loading: loadingFeedback } = useAsync(async () => {\n // Wait until entity is loaded\n if (!entity) {\n return;\n }\n\n try {\n const identity = await identityApi.getBackstageIdentity();\n const prevFeedback = await feedbackApi.getRatings(\n stringifyEntityRef(entity),\n );\n setRating(\n (prevFeedback.find(r => r.userRef === identity.userEntityRef)?.rating ??\n rating) as FeedbackRatings,\n );\n } catch (e) {\n errorApi.post(e as ErrorApiError);\n }\n }, [entity, feedbackApi, setRating]);\n\n const [{ loading: savingFeedback }, saveFeedback] = useAsyncFn(\n async (feedback: FeedbackRatings) => {\n try {\n await feedbackApi.recordRating(stringifyEntityRef(entity!), feedback);\n setRating(feedback);\n } catch (e) {\n errorApi.post(e as ErrorApiError);\n }\n },\n [entity, feedbackApi, setRating],\n );\n\n const applyRating = useCallback(\n (feedback: FeedbackRatings) => {\n // Clear rating if feedback is same as current\n if (feedback === rating) {\n saveFeedback(FeedbackRatings.neutral);\n return;\n }\n\n saveFeedback(feedback);\n if (feedback === FeedbackRatings.dislike && requestResponse) {\n setOpenFeedbackDialog(true);\n }\n },\n [rating, requestResponse, saveFeedback, setOpenFeedbackDialog],\n );\n\n if (loadingEntity || loadingFeedback || savingFeedback) {\n return <Progress />;\n }\n\n return (\n <>\n <IconButton\n data-testid=\"entity-feedback-like-button\"\n onClick={() => applyRating(FeedbackRatings.like)}\n >\n {rating === FeedbackRatings.like ? (\n <Tooltip title=\"Liked\">\n <ThumbUpIcon fontSize=\"small\" />\n </Tooltip>\n ) : (\n <Tooltip title=\"Like\">\n <ThumbUpOutlinedIcon fontSize=\"small\" />\n </Tooltip>\n )}\n </IconButton>\n <IconButton\n data-testid=\"entity-feedback-dislike-button\"\n onClick={() => applyRating(FeedbackRatings.dislike)}\n >\n {rating === FeedbackRatings.dislike ? (\n <Tooltip title=\"Disliked\">\n <ThumbDownIcon fontSize=\"small\" />\n </Tooltip>\n ) : (\n <Tooltip title=\"Dislike\">\n <ThumbDownOutlinedIcon fontSize=\"small\" />\n </Tooltip>\n )}\n </IconButton>\n <FeedbackResponseDialog\n entity={entity!}\n open={openFeedbackDialog}\n onClose={() => setOpenFeedbackDialog(false)}\n feedbackDialogResponses={feedbackDialogResponses}\n feedbackDialogTitle={feedbackDialogTitle}\n />\n </>\n );\n};\n"],"names":["FeedbackRatings"],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { stringifyEntityRef } from '@backstage/catalog-model';
|
|
2
|
+
import { Progress } from '@backstage/core-components';
|
|
3
|
+
import { useApi, errorApiRef, identityApiRef } from '@backstage/core-plugin-api';
|
|
4
|
+
import { useAsyncEntity } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import IconButton from '@material-ui/core/IconButton';
|
|
6
|
+
import StarOutlineIcon from '@material-ui/icons/StarOutline';
|
|
7
|
+
import StarIcon from '@material-ui/icons/Star';
|
|
8
|
+
import React, { useState, useCallback } from 'react';
|
|
9
|
+
import useAsync from 'react-use/esm/useAsync';
|
|
10
|
+
import useAsyncFn from 'react-use/esm/useAsyncFn';
|
|
11
|
+
import '@backstage/errors';
|
|
12
|
+
import { entityFeedbackApiRef } from '../index.esm.js';
|
|
13
|
+
import { F as FeedbackResponseDialog } from './FeedbackResponseDialog-FxuGsr7u.esm.js';
|
|
14
|
+
|
|
15
|
+
var FeedbackRatings = /* @__PURE__ */ ((FeedbackRatings2) => {
|
|
16
|
+
FeedbackRatings2[FeedbackRatings2["one"] = 1] = "one";
|
|
17
|
+
FeedbackRatings2[FeedbackRatings2["two"] = 2] = "two";
|
|
18
|
+
FeedbackRatings2[FeedbackRatings2["three"] = 3] = "three";
|
|
19
|
+
FeedbackRatings2[FeedbackRatings2["four"] = 4] = "four";
|
|
20
|
+
FeedbackRatings2[FeedbackRatings2["five"] = 5] = "five";
|
|
21
|
+
return FeedbackRatings2;
|
|
22
|
+
})(FeedbackRatings || {});
|
|
23
|
+
const StarredRatingButtons = (props) => {
|
|
24
|
+
const {
|
|
25
|
+
feedbackDialogResponses,
|
|
26
|
+
feedbackDialogTitle,
|
|
27
|
+
requestResponse = true,
|
|
28
|
+
requestResponseThreshold = 2 /* two */
|
|
29
|
+
} = props;
|
|
30
|
+
const errorApi = useApi(errorApiRef);
|
|
31
|
+
const feedbackApi = useApi(entityFeedbackApiRef);
|
|
32
|
+
const identityApi = useApi(identityApiRef);
|
|
33
|
+
const [rating, setRating] = useState();
|
|
34
|
+
const [openFeedbackDialog, setOpenFeedbackDialog] = useState(false);
|
|
35
|
+
const { entity, loading: loadingEntity } = useAsyncEntity();
|
|
36
|
+
const { loading: loadingFeedback } = useAsync(async () => {
|
|
37
|
+
var _a;
|
|
38
|
+
if (!entity) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const identity = await identityApi.getBackstageIdentity();
|
|
43
|
+
const prevFeedback = await feedbackApi.getRatings(
|
|
44
|
+
stringifyEntityRef(entity)
|
|
45
|
+
);
|
|
46
|
+
const prevRating = (_a = prevFeedback.find(
|
|
47
|
+
(r) => r.userRef === identity.userEntityRef
|
|
48
|
+
)) == null ? void 0 : _a.rating;
|
|
49
|
+
if (prevRating) {
|
|
50
|
+
setRating(parseInt(prevRating, 10));
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
errorApi.post(e);
|
|
54
|
+
}
|
|
55
|
+
}, [entity, feedbackApi, setRating]);
|
|
56
|
+
const [{ loading: savingFeedback }, saveFeedback] = useAsyncFn(
|
|
57
|
+
async (feedback) => {
|
|
58
|
+
try {
|
|
59
|
+
await feedbackApi.recordRating(
|
|
60
|
+
stringifyEntityRef(entity),
|
|
61
|
+
feedback.toString()
|
|
62
|
+
);
|
|
63
|
+
setRating(feedback);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
errorApi.post(e);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
[entity, feedbackApi, setRating]
|
|
69
|
+
);
|
|
70
|
+
const applyRating = useCallback(
|
|
71
|
+
(feedback) => {
|
|
72
|
+
if (feedback === rating) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
saveFeedback(feedback);
|
|
76
|
+
if (feedback <= requestResponseThreshold && requestResponse) {
|
|
77
|
+
setOpenFeedbackDialog(true);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
[
|
|
81
|
+
rating,
|
|
82
|
+
requestResponse,
|
|
83
|
+
requestResponseThreshold,
|
|
84
|
+
saveFeedback,
|
|
85
|
+
setOpenFeedbackDialog
|
|
86
|
+
]
|
|
87
|
+
);
|
|
88
|
+
if (loadingEntity || loadingFeedback || savingFeedback) {
|
|
89
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, Object.values(FeedbackRatings).filter((o) => typeof o === "number").map((starRating) => /* @__PURE__ */ React.createElement(
|
|
92
|
+
IconButton,
|
|
93
|
+
{
|
|
94
|
+
key: starRating,
|
|
95
|
+
"data-testid": `entity-feedback-star-button-${starRating}`,
|
|
96
|
+
onClick: () => applyRating(starRating)
|
|
97
|
+
},
|
|
98
|
+
rating && rating >= starRating ? /* @__PURE__ */ React.createElement(StarIcon, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(StarOutlineIcon, { fontSize: "small" })
|
|
99
|
+
)), /* @__PURE__ */ React.createElement(
|
|
100
|
+
FeedbackResponseDialog,
|
|
101
|
+
{
|
|
102
|
+
entity,
|
|
103
|
+
open: openFeedbackDialog,
|
|
104
|
+
onClose: () => setOpenFeedbackDialog(false),
|
|
105
|
+
feedbackDialogResponses,
|
|
106
|
+
feedbackDialogTitle
|
|
107
|
+
}
|
|
108
|
+
));
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { FeedbackRatings as F, StarredRatingButtons as S };
|
|
112
|
+
//# sourceMappingURL=StarredRatingButtons-7kGQPIqk.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StarredRatingButtons-7kGQPIqk.esm.js","sources":["../../src/components/StarredRatingButtons/StarredRatingButtons.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { Progress } from '@backstage/core-components';\nimport {\n ErrorApiError,\n errorApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\nimport { useAsyncEntity } from '@backstage/plugin-catalog-react';\nimport IconButton from '@material-ui/core/IconButton';\nimport StarOutlineIcon from '@material-ui/icons/StarOutline';\nimport StarIcon from '@material-ui/icons/Star';\nimport React, { ReactNode, useCallback, useState } from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport useAsyncFn from 'react-use/esm/useAsyncFn';\n\nimport { entityFeedbackApiRef } from '../../api';\nimport {\n EntityFeedbackResponse,\n FeedbackResponseDialog,\n} from '../FeedbackResponseDialog';\n\nexport enum FeedbackRatings {\n one = 1,\n two = 2,\n three = 3,\n four = 4,\n five = 5,\n}\n\n/**\n * @public\n */\nexport interface StarredRatingButtonsProps {\n feedbackDialogResponses?: EntityFeedbackResponse[];\n feedbackDialogTitle?: ReactNode;\n requestResponse?: boolean;\n requestResponseThreshold?: number;\n}\n\nexport const StarredRatingButtons = (props: StarredRatingButtonsProps) => {\n const {\n feedbackDialogResponses,\n feedbackDialogTitle,\n requestResponse = true,\n requestResponseThreshold = FeedbackRatings.two,\n } = props;\n const errorApi = useApi(errorApiRef);\n const feedbackApi = useApi(entityFeedbackApiRef);\n const identityApi = useApi(identityApiRef);\n const [rating, setRating] = useState<FeedbackRatings>();\n const [openFeedbackDialog, setOpenFeedbackDialog] = useState(false);\n const { entity, loading: loadingEntity } = useAsyncEntity();\n\n const { loading: loadingFeedback } = useAsync(async () => {\n // Wait until entity is loaded\n if (!entity) {\n return;\n }\n\n try {\n const identity = await identityApi.getBackstageIdentity();\n const prevFeedback = await feedbackApi.getRatings(\n stringifyEntityRef(entity),\n );\n\n const prevRating = prevFeedback.find(\n r => r.userRef === identity.userEntityRef,\n )?.rating;\n if (prevRating) {\n setRating(parseInt(prevRating, 10));\n }\n } catch (e) {\n errorApi.post(e as ErrorApiError);\n }\n }, [entity, feedbackApi, setRating]);\n\n const [{ loading: savingFeedback }, saveFeedback] = useAsyncFn(\n async (feedback: FeedbackRatings) => {\n try {\n await feedbackApi.recordRating(\n stringifyEntityRef(entity!),\n feedback.toString(),\n );\n setRating(feedback);\n } catch (e) {\n errorApi.post(e as ErrorApiError);\n }\n },\n [entity, feedbackApi, setRating],\n );\n\n const applyRating = useCallback(\n (feedback: FeedbackRatings) => {\n // Ignore rating if feedback is same as current\n if (feedback === rating) {\n return;\n }\n\n saveFeedback(feedback);\n if (feedback <= requestResponseThreshold && requestResponse) {\n setOpenFeedbackDialog(true);\n }\n },\n [\n rating,\n requestResponse,\n requestResponseThreshold,\n saveFeedback,\n setOpenFeedbackDialog,\n ],\n );\n\n if (loadingEntity || loadingFeedback || savingFeedback) {\n return <Progress />;\n }\n\n return (\n <>\n {Object.values(FeedbackRatings)\n .filter((o): o is number => typeof o === 'number')\n .map(starRating => (\n <IconButton\n key={starRating}\n data-testid={`entity-feedback-star-button-${starRating}`}\n onClick={() => applyRating(starRating as FeedbackRatings)}\n >\n {rating && rating >= starRating ? (\n <StarIcon fontSize=\"small\" />\n ) : (\n <StarOutlineIcon fontSize=\"small\" />\n )}\n </IconButton>\n ))}\n <FeedbackResponseDialog\n entity={entity!}\n open={openFeedbackDialog}\n onClose={() => setOpenFeedbackDialog(false)}\n feedbackDialogResponses={feedbackDialogResponses}\n feedbackDialogTitle={feedbackDialogTitle}\n />\n </>\n );\n};\n"],"names":["FeedbackRatings"],"mappings":"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"}
|