From 77cb573431afad27abf63ebb6f1de3f015f877d6 Mon Sep 17 00:00:00 2001 From: pratap0007 Date: Wed, 23 Dec 2020 10:40:39 +0530 Subject: [PATCH] UI:Adds rating This patch adds: - User can rate a particular resource - User can see their previous rating - User can update their rating - To rate a resource user needs to be authenticated to Hub Signed-off-by: Shiv Verma --- ui/src/components/Cards/Cards.css | 6 +- .../Cards/__snapshots__/Cards.test.tsx.snap | 28 ++- ui/src/components/Cards/index.tsx | 4 +- .../Icon/__snapshots__/Icon.test.tsx.snap | 2 +- ui/src/components/Icon/index.tsx | 2 +- ui/src/containers/Authentication/index.tsx | 2 +- .../containers/BasicDetails/BasicDetails.css | 4 + .../BasicDetails/BasicDetails.test.tsx | 5 + .../__snapshots__/BasicDetails.test.tsx.snap | 164 ++++++------ ui/src/containers/Details/Details.test.tsx | 5 + .../__snapshots__/Details.test.tsx.snap | 82 +++--- ui/src/containers/Details/index.tsx | 7 +- ui/src/containers/Rating/Rating.css | 4 - ui/src/containers/Rating/Rating.test.tsx | 111 +++++++- .../Rating/__snapshots__/Rating.test.tsx.snap | 237 +++++------------- ui/src/containers/Rating/index.tsx | 125 +++++++-- .../__snapshots__/Resources.test.tsx.snap | 112 +++++---- ui/src/containers/UserProfile/index.tsx | 8 +- ui/src/store/auth.test.ts | 4 + ui/src/store/auth.ts | 6 + 20 files changed, 527 insertions(+), 391 deletions(-) diff --git a/ui/src/components/Cards/Cards.css b/ui/src/components/Cards/Cards.css index f8f892532a..c3e426369e 100644 --- a/ui/src/components/Cards/Cards.css +++ b/ui/src/components/Cards/Cards.css @@ -7,7 +7,7 @@ } .hub-rating { - margin-top: 0.32em; + margin-top: 0.1em; } .hub-resource-name { @@ -44,6 +44,10 @@ vertical-align: -0.125em; } +.hub-rating-icon svg { + color: #484848; +} + .hub-card-link { text-decoration: none; } diff --git a/ui/src/components/Cards/__snapshots__/Cards.test.tsx.snap b/ui/src/components/Cards/__snapshots__/Cards.test.tsx.snap index 0c4304e212..0cbe318ff0 100644 --- a/ui/src/components/Cards/__snapshots__/Cards.test.tsx.snap +++ b/ui/src/components/Cards/__snapshots__/Cards.test.tsx.snap @@ -13,7 +13,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 4 @@ -57,7 +59,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 5 @@ -100,7 +104,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 5 @@ -143,7 +149,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 5 @@ -189,7 +197,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 5 @@ -233,7 +243,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 4.5 @@ -276,7 +288,9 @@ exports[`Cards should render the resources on cards 1`] = ` - + + + 4.5 diff --git a/ui/src/components/Cards/index.tsx b/ui/src/components/Cards/index.tsx index e37c3a776c..d337f900b0 100644 --- a/ui/src/components/Cards/index.tsx +++ b/ui/src/components/Cards/index.tsx @@ -48,7 +48,9 @@ const Cards: React.FC = (resources) => { - + + + {resource.rating} diff --git a/ui/src/components/Icon/__snapshots__/Icon.test.tsx.snap b/ui/src/components/Icon/__snapshots__/Icon.test.tsx.snap index 2d17888584..1f7e288cf1 100644 --- a/ui/src/components/Icon/__snapshots__/Icon.test.tsx.snap +++ b/ui/src/components/Icon/__snapshots__/Icon.test.tsx.snap @@ -10,7 +10,7 @@ exports[`Icon Component should render icon for Official Catalog 1`] = `""`; -exports[`Icon Component should render icon for Rating 1`] = `""`; +exports[`Icon Component should render icon for Rating 1`] = `""`; exports[`Icon Component should render icon for Task Kind 1`] = `""`; diff --git a/ui/src/components/Icon/index.tsx b/ui/src/components/Icon/index.tsx index 8014d1a352..bfede3da53 100644 --- a/ui/src/components/Icon/index.tsx +++ b/ui/src/components/Icon/index.tsx @@ -34,7 +34,7 @@ const Icon: React.FC = (props: Props) => { case Icons.Domain: return ; case Icons.Star: - return ; + return ; case Icons.Github: return ; } diff --git a/ui/src/containers/Authentication/index.tsx b/ui/src/containers/Authentication/index.tsx index 65adf5200e..e436e44330 100644 --- a/ui/src/containers/Authentication/index.tsx +++ b/ui/src/containers/Authentication/index.tsx @@ -44,7 +44,7 @@ const Authentication: React.FC = observer(() => { /> - {user.err ? ( + {user.authErr ? ( { return { + useHistory: () => { + return { + history: '' + }; + }, useParams: () => { return { name: 'buildah' diff --git a/ui/src/containers/BasicDetails/__snapshots__/BasicDetails.test.tsx.snap b/ui/src/containers/BasicDetails/__snapshots__/BasicDetails.test.tsx.snap index d752b1a2dc..5ee728c97d 100644 --- a/ui/src/containers/BasicDetails/__snapshots__/BasicDetails.test.tsx.snap +++ b/ui/src/containers/BasicDetails/__snapshots__/BasicDetails.test.tsx.snap @@ -120,8 +120,8 @@ exports[`length of DropdownItems should be 2 in case of buildah 1`] = `
- - + + @@ -140,64 +140,64 @@ exports[`length of DropdownItems should be 2 in case of buildah 1`] = `
- +
-
-
    -
  • - -
-
+
@@ -377,8 +377,8 @@ exports[`should render the BasicDetails component 1`] = `
- - + + @@ -397,64 +397,64 @@ exports[`should render the BasicDetails component 1`] = `
- +
-
-
    -
  • - -
-
+
diff --git a/ui/src/containers/Details/Details.test.tsx b/ui/src/containers/Details/Details.test.tsx index 034796c4f8..a6981497e4 100644 --- a/ui/src/containers/Details/Details.test.tsx +++ b/ui/src/containers/Details/Details.test.tsx @@ -15,6 +15,11 @@ const { Provider, root } = createProviderAndStore(api); jest.mock('react-router-dom', () => { return { + useHistory: () => { + return { + history: '' + }; + }, useParams: () => { return { name: 'buildah' diff --git a/ui/src/containers/Details/__snapshots__/Details.test.tsx.snap b/ui/src/containers/Details/__snapshots__/Details.test.tsx.snap index 48d64ffa22..5f01bec45b 100644 --- a/ui/src/containers/Details/__snapshots__/Details.test.tsx.snap +++ b/ui/src/containers/Details/__snapshots__/Details.test.tsx.snap @@ -121,8 +121,8 @@ exports[`Details component should render the details component 1`] = `
- - + + @@ -141,64 +141,64 @@ exports[`Details component should render the details component 1`] = `
- +
-
-
    -
  • - -
-
+
diff --git a/ui/src/containers/Details/index.tsx b/ui/src/containers/Details/index.tsx index 76825eb6e6..ca63eda0d4 100644 --- a/ui/src/containers/Details/index.tsx +++ b/ui/src/containers/Details/index.tsx @@ -5,15 +5,20 @@ import { Spinner } from '@patternfly/react-core'; import { useMst } from '../../store/root'; import BasicDetails from '../BasicDetails'; import Description from '../../components/Description'; +import { assert } from '../../store/utils'; const Details: React.FC = () => { - const { resources } = useMst(); + const { resources, user } = useMst(); const { name } = useParams(); const resourceDetails = () => { resources.versionInfo(name); resources.loadReadme(name); resources.loadYaml(name); + + const resource = resources.resources.get(name); + assert(resource); + user.getRating(resource.id); }; return useObserver(() => diff --git a/ui/src/containers/Rating/Rating.css b/ui/src/containers/Rating/Rating.css index abf3afc384..73ca996298 100644 --- a/ui/src/containers/Rating/Rating.css +++ b/ui/src/containers/Rating/Rating.css @@ -23,10 +23,6 @@ color: lightgrey; } -.hub-rate-area > label > svg { - color: #484848; -} - .hub-rate-area:not(:checked) > label:before { content: ''; } diff --git a/ui/src/containers/Rating/Rating.test.tsx b/ui/src/containers/Rating/Rating.test.tsx index d788ad287b..200fc9b893 100644 --- a/ui/src/containers/Rating/Rating.test.tsx +++ b/ui/src/containers/Rating/Rating.test.tsx @@ -1,8 +1,111 @@ import React from 'react'; -import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; +import { when } from 'mobx'; +import { StarIcon } from '@patternfly/react-icons'; import Rating from '.'; +import { FakeHub } from '../../api/testutil'; +import { createProviderAndStore } from '../../store/root'; -it('should render Ratings component', () => { - const component = renderer.create().toJSON(); - expect(component).toMatchSnapshot(); +const TESTDATA_DIR = `src/store/testdata`; +const api = new FakeHub(TESTDATA_DIR); +const { Provider, root } = createProviderAndStore(api); + +jest.mock('react-router-dom', () => { + return { + useHistory: () => { + return { + history: '' + }; + }, + useParams: () => { + return { + name: 'buildah' + }; + } + }; +}); + +describe('Rating ', () => { + it('should render Ratings component', () => { + const component = mount( + + + + ); + expect(component.debug()).toMatchSnapshot(); + }); + + it('should find star Icon', () => { + const component = mount( + + + + ); + expect(component.find(StarIcon).length).toBe(5); + }); + + it('should update user rating for a resource', (done) => { + const { user } = root; + + const code = { + code: 'foo' + }; + + user.authenticate(code, 'baar'); + + when( + () => !user.isLoading, + () => { + user.getRating(13); + when( + () => !user.isLoading, + () => { + expect(user.userRating).toBe(2); + user.setRating(13, 3); + + when( + () => !user.isLoading, + () => { + const component = mount( + + + + ); + + expect(component.find('input').get(Number('2')).props.checked).toBe(true); + done(); + } + ); + } + ); + } + ); + }); + + it('should find user rating for a resource', (done) => { + const { user } = root; + const code = { + code: 'foo' + }; + user.authenticate(code, 'baar'); + when( + () => !user.isLoading, + () => { + user.getRating(13); + when( + () => !user.isLoading, + () => { + expect(user.userRating).toBe(2); + const component = mount( + + + + ); + expect(component.find('input').get(Number('3')).props.checked).toBe(true); + done(); + } + ); + } + ); + }); }); diff --git a/ui/src/containers/Rating/__snapshots__/Rating.test.tsx.snap b/ui/src/containers/Rating/__snapshots__/Rating.test.tsx.snap index 420141ee09..8ce5d1ca20 100644 --- a/ui/src/containers/Rating/__snapshots__/Rating.test.tsx.snap +++ b/ui/src/containers/Rating/__snapshots__/Rating.test.tsx.snap @@ -1,177 +1,66 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render Ratings component 1`] = ` -
-
-
    -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
-
-
+exports[`Rating should render Ratings component 1`] = ` +" + + +
+
+
    + + + + + + + + + + +
+
+
+
+ +
" `; diff --git a/ui/src/containers/Rating/index.tsx b/ui/src/containers/Rating/index.tsx index f8b6c2e7cf..647ad2dfc9 100644 --- a/ui/src/containers/Rating/index.tsx +++ b/ui/src/containers/Rating/index.tsx @@ -1,24 +1,115 @@ -import React from 'react'; -import { StarIcon } from '@patternfly/react-icons'; +import React, { useState } from 'react'; +import { observer } from 'mobx-react'; +import { useHistory, useParams } from 'react-router-dom'; +import { IconSize } from '@patternfly/react-icons'; +import { Alert, AlertActionCloseButton, AlertGroup } from '@patternfly/react-core'; +import { useMst } from '../../store/root'; +import { assert } from '../../store/utils'; +import { Icons } from '../../common/icons'; +import Icon from '../../components/Icon'; import './Rating.css'; -const Rating: React.FC = () => { +const Rating: React.FC = observer(() => { + const { name } = useParams(); + + const [star, setStar] = useState([false, false, false, false, false]); + const [ratingError, setRatingError] = useState(''); + + const highlightStar = (value: number) => { + const checkedStatus = [false, false, false, false, false]; + switch (value) { + case 5: + checkedStatus[4] = true; + break; + case 4: + checkedStatus[3] = true; + break; + case 3: + checkedStatus[2] = true; + break; + case 2: + checkedStatus[1] = true; + break; + case 1: + checkedStatus[0] = true; + break; + default: + } + setStar(checkedStatus); + }; + + const starList = ( +
    + + + + + + + + + + +
+ ); + + const { user, resources } = useMst(); + const history = useHistory(); + const rateResource = (event: React.MouseEvent) => { + const target = event.target as HTMLInputElement; + const rating = target.value; + + if (!user.isAuthenticated) { + history.push('/login'); + } else { + if (rating) { + const resource = resources.resources.get(name); + assert(resource); + user.setRating(resource.id, Number(rating)); + if (user.ratingErr === '') { + highlightStar(Number(rating)); + } else { + setRatingError(user.ratingErr); + } + } + } + }; + + if (!user.isLoading) { + highlightStar(user.userRating); + user.setLoading(true); + } + return (
-
-
    - {Array.apply(0, Array(5)).map((_, element) => ( -
  • - - -
  • - ))} -
-
+
{starList}
+ {ratingError ? ( + + { + window.location.reload(); + }} + /> + } + > + + ) : null}
); -}; - +}); export default Rating; diff --git a/ui/src/containers/Resources/__snapshots__/Resources.test.tsx.snap b/ui/src/containers/Resources/__snapshots__/Resources.test.tsx.snap index 1d7f59e3d9..460025f807 100644 --- a/ui/src/containers/Resources/__snapshots__/Resources.test.tsx.snap +++ b/ui/src/containers/Resources/__snapshots__/Resources.test.tsx.snap @@ -38,13 +38,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
4 @@ -124,13 +126,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
5 @@ -209,13 +213,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
5 @@ -294,13 +300,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
5 @@ -384,13 +392,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
5 @@ -470,13 +480,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
4.5 @@ -555,13 +567,15 @@ exports[`Resource Component should render the resources component 1`] = `
- - - - - - - + + + + + + + + +
4.5 diff --git a/ui/src/containers/UserProfile/index.tsx b/ui/src/containers/UserProfile/index.tsx index a2b5cff9a3..a771aadd1d 100644 --- a/ui/src/containers/UserProfile/index.tsx +++ b/ui/src/containers/UserProfile/index.tsx @@ -15,12 +15,6 @@ import './UserProfile.css'; const UserProfile: React.FC = () => { const { user } = useMst(); - const logout = () => { - localStorage.clear(); - user.setIsAuthenticated(false); - user.setLoading(true); - }; - const [isModalOpen, setIsModalOpen] = useState(false); const [isOpen, set] = useState(false); @@ -30,7 +24,7 @@ const UserProfile: React.FC = () => { setIsModalOpen(!isModalOpen)}> Copy Hub Token , - + Logout ]; diff --git a/ui/src/store/auth.test.ts b/ui/src/store/auth.test.ts index 5ea3234747..ca2593a8a9 100644 --- a/ui/src/store/auth.test.ts +++ b/ui/src/store/auth.test.ts @@ -56,6 +56,10 @@ describe('Store functions', () => { store.setLoading(false); expect(store.isLoading).toBe(false); + store.logout(); + expect(store.isAuthenticated).toBe(false); + expect(store.userRating).toBe(0); + done(); } ); diff --git a/ui/src/store/auth.ts b/ui/src/store/auth.ts index 858a89ce7a..0f81fa99a2 100644 --- a/ui/src/store/auth.ts +++ b/ui/src/store/auth.ts @@ -49,6 +49,12 @@ export const AuthStore = types }, onFailure(err: Error) { self.authErr = err.toString(); + }, + logout() { + localStorage.clear(); + self.isAuthenticated = false; + self.isLoading = false; + self.userRating = 0; } })) .views((self) => ({