From 73869eee6928d2d137da7ad9c6c3bb3badf2f046 Mon Sep 17 00:00:00 2001 From: devrk96 Date: Wed, 11 Nov 2020 18:42:05 +0530 Subject: [PATCH] Feature: Show org user icons on homepage (#1685) * Displaying org user icons feature is implemented * using username if name not found in user details * Share user icon cypress test implemented --- .../OrganisationTests/OrgUserIconTest_spec.js | 25 ++++++++ app/client/cypress/locators/HomePage.json | 3 +- app/client/src/api/ApplicationApi.tsx | 7 +++ app/client/src/pages/Applications/index.tsx | 62 ++++++++++++++----- .../src/pages/common/ProfileDropdown.tsx | 29 +-------- app/client/src/pages/common/ProfileImage.tsx | 40 ++++++++++++ .../pages/organization/OrgInviteUsersForm.tsx | 10 +-- app/client/src/sagas/ApplicationSagas.tsx | 1 + 8 files changed, 126 insertions(+), 51 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/OrgUserIconTest_spec.js create mode 100644 app/client/src/pages/common/ProfileImage.tsx diff --git a/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/OrgUserIconTest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/OrgUserIconTest_spec.js new file mode 100644 index 0000000000..9b45f42465 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/OrganisationTests/OrgUserIconTest_spec.js @@ -0,0 +1,25 @@ +/// + +const homePage = require("../../../locators/HomePage.json"); + +describe("Check if org has user icons on homepage", function() { + let orgid; + + it("create org and check if user icons exists in that org on homepage", function() { + cy.NavigateToHome(); + cy.generateUUID().then(uid => { + orgid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(orgid); + cy.get(homePage.orgList.concat(orgid).concat(")")) + .scrollIntoView() + .should("be.visible") + .within(() => { + cy.get(homePage.shareUserIcons) + .first() + .should("be.visible"); + }); + }); + cy.LogOut(); + }); +}); diff --git a/app/client/cypress/locators/HomePage.json b/app/client/cypress/locators/HomePage.json index 3a31c08531..728dd5800e 100644 --- a/app/client/cypress/locators/HomePage.json +++ b/app/client/cypress/locators/HomePage.json @@ -61,5 +61,6 @@ "orgWebsiteInput": "[data-cy=t--org-website-input]", "orgHeaderName": ".t--organization-header", "leftPanelContainer": "[data-cy=t--left-panel]", - "themeText": "label div" + "themeText": "label div", + "shareUserIcons": ".org-share-user-icons" } diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index fe630e57c9..693c0940b0 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -83,12 +83,19 @@ export interface ApplicationObject { userPermissions: string[]; } +export interface UserRoles { + name: string; + roleName: string; + username: string; +} + export interface OrganizationApplicationObject { applications: Array; organization: { id: string; name: string; }; + userRoles: Array; } export interface FetchUsersApplicationsOrgsResponse extends ApiResponse { data: { diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 660c9374be..665de33701 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -28,10 +28,7 @@ import FormDialogComponent from "components/editorComponents/form/FormDialogComp import { User } from "constants/userConstants"; import { getCurrentUser } from "selectors/usersSelectors"; import CreateOrganizationForm from "pages/organization/CreateOrganizationForm"; -import { - CREATE_ORGANIZATION_FORM_NAME, - CREATE_APPLICATION_FORM_NAME, -} from "constants/forms"; +import { CREATE_ORGANIZATION_FORM_NAME } from "constants/forms"; import { getOnSelectAction, DropdownOnSelectActions, @@ -48,7 +45,7 @@ import { Classes } from "components/ads/common"; import Menu from "components/ads/Menu"; import { Position } from "@blueprintjs/core/lib/esm/common/position"; import HelpModal from "components/designSystems/appsmith/help/HelpModal"; -import { UpdateApplicationPayload } from "api/ApplicationApi"; +import { UpdateApplicationPayload, UserRoles } from "api/ApplicationApi"; import PerformanceTracker, { PerformanceTransactionName, } from "utils/PerformanceTracker"; @@ -58,6 +55,7 @@ import CenteredWrapper from "../../components/designSystems/appsmith/CenteredWra import NoSearchImage from "../../assets/images/NoSearchResult.svg"; import { getNextEntityName } from "utils/AppsmithUtils"; import Spinner from "components/ads/Spinner"; +import ProfileImage from "pages/common/ProfileImage"; const OrgDropDown = styled.div` display: flex; @@ -193,6 +191,25 @@ const ItemWrapper = styled.div` const StyledIcon = styled(Icon)` margin-right: 11px; `; +const UserImageContainer = styled.div` + display: flex; + margin-right: 8px; + + div { + cursor: default; + margin-right: -6px; + width: 24px; + height: 24px; + } + + div:last-child { + margin-right: 0px; + } +`; +const OrgShareUsers = styled.div` + display: flex; + align-items: center; +`; function Item(props: { label: string; @@ -536,7 +553,7 @@ const ApplicationsSection = (props: any) => { } else { organizationsListComponent = updatedOrgs.map( (organizationObject: any, index: number) => { - const { organization, applications } = organizationObject; + const { organization, applications, userRoles } = organizationObject; const hasManageOrgPermissions = isPermitted( organization.userPermissions, PERMISSION_TYPE.MANAGE_ORGANIZATION, @@ -572,15 +589,30 @@ const ApplicationsSection = (props: any) => { PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION, ) && !isFetchingApplications && ( - - } - canOutsideClickClose={true} - Form={OrgInviteUsersForm} - orgId={organization.id} - title={`Invite Users to ${organization.name}`} - /> + + + {userRoles.map((el: UserRoles) => ( + + ))} + + + } + canOutsideClickClose={true} + Form={OrgInviteUsersForm} + orgId={organization.id} + title={`Invite Users to ${organization.name}`} + /> + )} diff --git a/app/client/src/pages/common/ProfileDropdown.tsx b/app/client/src/pages/common/ProfileDropdown.tsx index 77608a724f..c27296b588 100644 --- a/app/client/src/pages/common/ProfileDropdown.tsx +++ b/app/client/src/pages/common/ProfileDropdown.tsx @@ -1,8 +1,5 @@ import React, { Fragment } from "react"; import { CommonComponentProps, Classes } from "components/ads/common"; -import { getInitialsAndColorCode } from "utils/AppsmithUtils"; -import { useSelector } from "react-redux"; -import { getThemeDetails } from "selectors/themeSelectors"; import Text, { TextType } from "components/ads/Text"; import styled, { createGlobalStyle } from "styled-components"; import { Position } from "@blueprintjs/core"; @@ -15,23 +12,13 @@ import { DropdownOnSelectActions, } from "./CustomizedDropdown/dropdownHelpers"; import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import ProfileImage from "./ProfileImage"; type TagProps = CommonComponentProps & { onClick?: (text: string) => void; userName?: string; }; -export const ProfileImage = styled.div<{ backgroundColor?: string }>` - width: 34px; - height: 34px; - display: flex; - align-items: center; - border-radius: 50%; - justify-content: center; - cursor: pointer; - background-color: ${props => props.backgroundColor}; -`; - const ProfileMenuStyle = createGlobalStyle` .bp3-popover { box-shadow: none; @@ -67,20 +54,8 @@ const UserInformation = styled.div` `; export default function ProfileDropdown(props: TagProps) { - const themeDetails = useSelector(getThemeDetails); + const Profile = ; - const initialsAndColorCode = getInitialsAndColorCode( - props.userName, - themeDetails.theme.colors.appCardColors, - ); - - const Profile = ( - - - {initialsAndColorCode[0]} - - - ); return ( diff --git a/app/client/src/pages/common/ProfileImage.tsx b/app/client/src/pages/common/ProfileImage.tsx new file mode 100644 index 0000000000..83eef9b28f --- /dev/null +++ b/app/client/src/pages/common/ProfileImage.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { getThemeDetails } from "selectors/themeSelectors"; +import { getInitialsAndColorCode } from "utils/AppsmithUtils"; +import Text, { TextType } from "components/ads/Text"; +import styled from "styled-components"; + +export const Profile = styled.div<{ backgroundColor?: string }>` + width: 34px; + height: 34px; + display: flex; + align-items: center; + border-radius: 50%; + justify-content: center; + cursor: pointer; + background-color: ${props => props.backgroundColor}; +`; + +export default function ProfileImage(props: { + userName?: string; + className?: string; +}) { + const themeDetails = useSelector(getThemeDetails); + + const initialsAndColorCode = getInitialsAndColorCode( + props.userName, + themeDetails.theme.colors.appCardColors, + ); + + return ( + + + {initialsAndColorCode[0]} + + + ); +} diff --git a/app/client/src/pages/organization/OrgInviteUsersForm.tsx b/app/client/src/pages/organization/OrgInviteUsersForm.tsx index a021b08207..6618083799 100644 --- a/app/client/src/pages/organization/OrgInviteUsersForm.tsx +++ b/app/client/src/pages/organization/OrgInviteUsersForm.tsx @@ -38,8 +38,8 @@ import { Classes } from "components/ads/common"; import Callout from "components/ads/Callout"; import { getInitialsAndColorCode } from "utils/AppsmithUtils"; import { getThemeDetails } from "selectors/themeSelectors"; -import { ProfileImage } from "pages/common/ProfileDropdown"; import { scrollbarDark } from "constants/DefaultTheme"; +import ProfileImage from "pages/common/ProfileImage"; const OrgInviteTitle = styled.div` padding: 10px 0px; @@ -280,7 +280,6 @@ const OrgInviteUsersForm = (props: any) => { ); return { ...user, - imageBackground: details[1], initials: details[0], }; }, @@ -358,18 +357,13 @@ const OrgInviteUsersForm = (props: any) => { username: string; name: string; roleName: string; - imageBackground: string; initials: string; }) => { return ( - - - {user.initials} - - + {user.name} {user.username} diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index d7dd6d6983..686f083382 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -88,6 +88,7 @@ export function* getAllApplicationSaga() { const organizationApplication: OrganizationApplicationObject[] = response.data.organizationApplications.map( (userOrgs: OrganizationApplicationObject) => ({ organization: userOrgs.organization, + userRoles: userOrgs.userRoles, applications: !userOrgs.applications ? [] : userOrgs.applications.map((application: ApplicationObject) => {