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
This commit is contained in:
devrk96 2020-11-11 18:42:05 +05:30 committed by GitHub
parent a4f652ce70
commit 73869eee69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 51 deletions

View File

@ -0,0 +1,25 @@
/// <reference types="Cypress" />
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();
});
});

View File

@ -61,5 +61,6 @@
"orgWebsiteInput": "[data-cy=t--org-website-input]", "orgWebsiteInput": "[data-cy=t--org-website-input]",
"orgHeaderName": ".t--organization-header", "orgHeaderName": ".t--organization-header",
"leftPanelContainer": "[data-cy=t--left-panel]", "leftPanelContainer": "[data-cy=t--left-panel]",
"themeText": "label div" "themeText": "label div",
"shareUserIcons": ".org-share-user-icons"
} }

View File

@ -83,12 +83,19 @@ export interface ApplicationObject {
userPermissions: string[]; userPermissions: string[];
} }
export interface UserRoles {
name: string;
roleName: string;
username: string;
}
export interface OrganizationApplicationObject { export interface OrganizationApplicationObject {
applications: Array<ApplicationObject>; applications: Array<ApplicationObject>;
organization: { organization: {
id: string; id: string;
name: string; name: string;
}; };
userRoles: Array<UserRoles>;
} }
export interface FetchUsersApplicationsOrgsResponse extends ApiResponse { export interface FetchUsersApplicationsOrgsResponse extends ApiResponse {
data: { data: {

View File

@ -28,10 +28,7 @@ import FormDialogComponent from "components/editorComponents/form/FormDialogComp
import { User } from "constants/userConstants"; import { User } from "constants/userConstants";
import { getCurrentUser } from "selectors/usersSelectors"; import { getCurrentUser } from "selectors/usersSelectors";
import CreateOrganizationForm from "pages/organization/CreateOrganizationForm"; import CreateOrganizationForm from "pages/organization/CreateOrganizationForm";
import { import { CREATE_ORGANIZATION_FORM_NAME } from "constants/forms";
CREATE_ORGANIZATION_FORM_NAME,
CREATE_APPLICATION_FORM_NAME,
} from "constants/forms";
import { import {
getOnSelectAction, getOnSelectAction,
DropdownOnSelectActions, DropdownOnSelectActions,
@ -48,7 +45,7 @@ import { Classes } from "components/ads/common";
import Menu from "components/ads/Menu"; import Menu from "components/ads/Menu";
import { Position } from "@blueprintjs/core/lib/esm/common/position"; import { Position } from "@blueprintjs/core/lib/esm/common/position";
import HelpModal from "components/designSystems/appsmith/help/HelpModal"; import HelpModal from "components/designSystems/appsmith/help/HelpModal";
import { UpdateApplicationPayload } from "api/ApplicationApi"; import { UpdateApplicationPayload, UserRoles } from "api/ApplicationApi";
import PerformanceTracker, { import PerformanceTracker, {
PerformanceTransactionName, PerformanceTransactionName,
} from "utils/PerformanceTracker"; } from "utils/PerformanceTracker";
@ -58,6 +55,7 @@ import CenteredWrapper from "../../components/designSystems/appsmith/CenteredWra
import NoSearchImage from "../../assets/images/NoSearchResult.svg"; import NoSearchImage from "../../assets/images/NoSearchResult.svg";
import { getNextEntityName } from "utils/AppsmithUtils"; import { getNextEntityName } from "utils/AppsmithUtils";
import Spinner from "components/ads/Spinner"; import Spinner from "components/ads/Spinner";
import ProfileImage from "pages/common/ProfileImage";
const OrgDropDown = styled.div` const OrgDropDown = styled.div`
display: flex; display: flex;
@ -193,6 +191,25 @@ const ItemWrapper = styled.div`
const StyledIcon = styled(Icon)` const StyledIcon = styled(Icon)`
margin-right: 11px; 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: { function Item(props: {
label: string; label: string;
@ -536,7 +553,7 @@ const ApplicationsSection = (props: any) => {
} else { } else {
organizationsListComponent = updatedOrgs.map( organizationsListComponent = updatedOrgs.map(
(organizationObject: any, index: number) => { (organizationObject: any, index: number) => {
const { organization, applications } = organizationObject; const { organization, applications, userRoles } = organizationObject;
const hasManageOrgPermissions = isPermitted( const hasManageOrgPermissions = isPermitted(
organization.userPermissions, organization.userPermissions,
PERMISSION_TYPE.MANAGE_ORGANIZATION, PERMISSION_TYPE.MANAGE_ORGANIZATION,
@ -572,15 +589,30 @@ const ApplicationsSection = (props: any) => {
PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION, PERMISSION_TYPE.INVITE_USER_TO_ORGANIZATION,
) && ) &&
!isFetchingApplications && ( !isFetchingApplications && (
<FormDialogComponent <OrgShareUsers>
trigger={ <UserImageContainer>
<Button text={"Share"} icon={"share"} size={Size.small} /> {userRoles.map((el: UserRoles) => (
} <ProfileImage
canOutsideClickClose={true} className="org-share-user-icons"
Form={OrgInviteUsersForm} userName={el.name ? el.name : el.username}
orgId={organization.id} key={el.username}
title={`Invite Users to ${organization.name}`} />
/> ))}
</UserImageContainer>
<FormDialogComponent
trigger={
<Button
text={"Share"}
icon={"share"}
size={Size.small}
/>
}
canOutsideClickClose={true}
Form={OrgInviteUsersForm}
orgId={organization.id}
title={`Invite Users to ${organization.name}`}
/>
</OrgShareUsers>
)} )}
</OrgDropDown> </OrgDropDown>
<ApplicationCardsWrapper key={organization.id}> <ApplicationCardsWrapper key={organization.id}>

View File

@ -1,8 +1,5 @@
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { CommonComponentProps, Classes } from "components/ads/common"; 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 Text, { TextType } from "components/ads/Text";
import styled, { createGlobalStyle } from "styled-components"; import styled, { createGlobalStyle } from "styled-components";
import { Position } from "@blueprintjs/core"; import { Position } from "@blueprintjs/core";
@ -15,23 +12,13 @@ import {
DropdownOnSelectActions, DropdownOnSelectActions,
} from "./CustomizedDropdown/dropdownHelpers"; } from "./CustomizedDropdown/dropdownHelpers";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import ProfileImage from "./ProfileImage";
type TagProps = CommonComponentProps & { type TagProps = CommonComponentProps & {
onClick?: (text: string) => void; onClick?: (text: string) => void;
userName?: string; 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` const ProfileMenuStyle = createGlobalStyle`
.bp3-popover { .bp3-popover {
box-shadow: none; box-shadow: none;
@ -67,20 +54,8 @@ const UserInformation = styled.div`
`; `;
export default function ProfileDropdown(props: TagProps) { export default function ProfileDropdown(props: TagProps) {
const themeDetails = useSelector(getThemeDetails); const Profile = <ProfileImage userName={props.userName} />;
const initialsAndColorCode = getInitialsAndColorCode(
props.userName,
themeDetails.theme.colors.appCardColors,
);
const Profile = (
<ProfileImage backgroundColor={initialsAndColorCode[1]}>
<Text type={TextType.H6} highlight>
{initialsAndColorCode[0]}
</Text>
</ProfileImage>
);
return ( return (
<Fragment> <Fragment>
<ProfileMenuStyle /> <ProfileMenuStyle />

View File

@ -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 (
<Profile
backgroundColor={initialsAndColorCode[1]}
className={props.className}
>
<Text type={TextType.H6} highlight>
{initialsAndColorCode[0]}
</Text>
</Profile>
);
}

View File

@ -38,8 +38,8 @@ import { Classes } from "components/ads/common";
import Callout from "components/ads/Callout"; import Callout from "components/ads/Callout";
import { getInitialsAndColorCode } from "utils/AppsmithUtils"; import { getInitialsAndColorCode } from "utils/AppsmithUtils";
import { getThemeDetails } from "selectors/themeSelectors"; import { getThemeDetails } from "selectors/themeSelectors";
import { ProfileImage } from "pages/common/ProfileDropdown";
import { scrollbarDark } from "constants/DefaultTheme"; import { scrollbarDark } from "constants/DefaultTheme";
import ProfileImage from "pages/common/ProfileImage";
const OrgInviteTitle = styled.div` const OrgInviteTitle = styled.div`
padding: 10px 0px; padding: 10px 0px;
@ -280,7 +280,6 @@ const OrgInviteUsersForm = (props: any) => {
); );
return { return {
...user, ...user,
imageBackground: details[1],
initials: details[0], initials: details[0],
}; };
}, },
@ -358,18 +357,13 @@ const OrgInviteUsersForm = (props: any) => {
username: string; username: string;
name: string; name: string;
roleName: string; roleName: string;
imageBackground: string;
initials: string; initials: string;
}) => { }) => {
return ( return (
<Fragment key={user.username}> <Fragment key={user.username}>
<User> <User>
<UserInfo> <UserInfo>
<ProfileImage backgroundColor={user.imageBackground}> <ProfileImage userName={user.initials} />
<Text type={TextType.H6} highlight>
{user.initials}
</Text>
</ProfileImage>
<UserName> <UserName>
<Text type={TextType.H5}>{user.name}</Text> <Text type={TextType.H5}>{user.name}</Text>
<Text type={TextType.P2}>{user.username}</Text> <Text type={TextType.P2}>{user.username}</Text>

View File

@ -88,6 +88,7 @@ export function* getAllApplicationSaga() {
const organizationApplication: OrganizationApplicationObject[] = response.data.organizationApplications.map( const organizationApplication: OrganizationApplicationObject[] = response.data.organizationApplications.map(
(userOrgs: OrganizationApplicationObject) => ({ (userOrgs: OrganizationApplicationObject) => ({
organization: userOrgs.organization, organization: userOrgs.organization,
userRoles: userOrgs.userRoles,
applications: !userOrgs.applications applications: !userOrgs.applications
? [] ? []
: userOrgs.applications.map((application: ApplicationObject) => { : userOrgs.applications.map((application: ApplicationObject) => {