UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

283 lines (282 loc) 13.8 kB
import "../../CommonImports"; import "../../Core/core.css"; import "./IdentityCard.css"; /** * The IdentityCard is intended to show contact and organization information for an identity. * You may pass the entity directly or you may pass a unique attribute (e.g. uniqueName, entityID, signInAddress) as a prop. * */ import * as React from "react"; import { CardType } from "./IdentityCard.Props"; import { IdentityCardContent } from "./IdentityCardContent"; import * as Utils from "./IdentityCardIdentityUtils"; export class IdentityCard extends React.Component { constructor(props) { super(props); this.calloutRef = React.createRef(); this.onDismissCallback = () => { this.props.onDismissCallback && this.props.onDismissCallback(); }; // Handle going back this.headerOnClickHandler = () => { if (this.state.dataState.previousDataState) { this.setState({ dataState: this.state.dataState.previousDataState, working: false }); } else if (this.props.initialHeader && this.props.initialHeader.onClickFunction) { this.props.initialHeader.onClickFunction(); } }; this.onShowContactCard = () => { const newDataState = Object.assign(Object.assign({}, this.state.dataState), { previousDataState: this.state.dataState, cardType: CardType.Contact, header: this.state.dataState.identity }); this.setState({ dataState: newDataState }); }; this.onShowOrganizationCard = () => { // Do not handle click event if working if (this.state.working || this.dismissed) { return; } const currentDataState = this.state.dataState; if (currentDataState.isGroup) { // Only make the call if we don't have the data already const currentDataState = this.state.dataState; const newDataState = Object.assign(Object.assign({}, currentDataState), { cardType: CardType.Organization, header: currentDataState.identity, previousDataState: currentDataState }); this.setState({ dataState: newDataState }); if (!currentDataState.successors || currentDataState.successors.length <= 1) { this.setState({ working: true }); this.resolveIdentity(newDataState, this.props.onRequestConnectionInformation(currentDataState.identity)); } } else if (currentDataState.managerList && currentDataState.managerList.length <= 1) { // Only make the call if we don't have the data already const currentDataState = this.state.dataState; const newDataState = Object.assign(Object.assign({}, currentDataState), { cardType: CardType.Organization, header: currentDataState.identity, previousDataState: currentDataState }); this.setState({ dataState: newDataState, working: true }); this.resolveIdentity(newDataState, this.props.onRequestConnectionInformation(currentDataState.identity, true)); } else { // Already have the data so we can present const currentDataState = this.state.dataState; const newDataState = Object.assign(Object.assign({}, currentDataState), { cardType: CardType.Organization, header: currentDataState.identity, previousDataState: currentDataState }); this.setState({ dataState: newDataState, working: false }); } }; // Handle entity click this.onClickEntity = (identity) => { // Do not handle click event if working if (this.state.working) { return; } const currentDataState = this.state.dataState; const newDataState = { identity: identity, cardType: CardType.Default, header: currentDataState.identity, isGroup: Utils.isGroup(currentDataState.identity), directReportList: [], managerList: [], previousDataState: currentDataState, displayName: identity.displayName, imageUrl: identity.image, email: identity.mail }; this.setState({ dataState: newDataState, working: true }); // API call to get identity this.getIdentityByUniqueAttribute(identity); }; this.updateConnections = (dataState, connections) => { // Don't call if the component is unmounted. if (this.dismissed) { return; } // ensure we haven't attempted to update which card we were in before the callback happens. if (this.state.dataState && dataState.identity === this.state.dataState.identity) { this.setState({ dataState: Object.assign(Object.assign({}, this.state.dataState), { managerList: connections.managers && connections.managers.reverse(), directReportList: connections.directReports, successors: connections.successors }), working: false }); } }; this.updateEntity = (identity) => { if (!identity) { // The identity could not be found this.setState({ working: false }); return; } if (!Utils.isCompleteIdentity(identity, true)) { // The identity was found but doesn't have enough info to display this.setState({ working: false, showUnknownUser: true }); return; } const requireConnections = (Utils.isGroup(identity) && (!this.state.dataState.successors || this.state.dataState.successors.length <= 0)) || Utils.isAadUser(identity) || Utils.isAdUser(identity); const imageUrl = this.props.imageUrl ? this.props.imageUrl : identity.image; // Update data state and working state (visible state updated later) const newDataState = Object.assign(Object.assign({}, this.state.dataState), { identity: identity, displayName: identity.displayName, imageUrl: imageUrl, isGroup: Utils.isGroup(identity) }); // Check for authenticated users. For authenticated users, go on to make the connections call. For non-authenticated users, stop the calls and load the card with images. if (requireConnections) { this.setState({ dataState: newDataState, working: false }); this.resolveIdentity(newDataState, this.props.onRequestConnectionInformation(newDataState.identity)); } else { this.setState({ dataState: newDataState, working: false }); } }; // Setup state data and history const initialDataState = { identity: props.identity, managerList: undefined, directReportList: undefined, previousDataState: undefined, cardType: CardType.Default, header: props.initialHeader ? props.initialHeader.identity : undefined, displayName: props.displayName, imageUrl: props.imageUrl, email: this.getEmail(), isGroup: Utils.isGroup(props.identity) }; let moreWorkNeeded = true; if (!props.identity && !props.uniqueAttribute && props.displayName) { // All we have is displayName and may be imageUrl. Simply go ahead and setup the state for displaying the card without any identity call. initialDataState.identity = { entityId: "", entityType: "user", originDirectory: "vsd", originId: "", displayName: props.displayName, image: props.imageUrl, signInAddress: initialDataState.email }; moreWorkNeeded = false; } this.state = { dataState: initialDataState, showUnknownUser: false, working: moreWorkNeeded }; } componentDidMount() { if (this.state.working) { this.setupInitialData(this.props.uniqueAttribute); } } componentDidUpdate() { // If the content was updated, we may need to update the callout so it is positioned correctly. this.calloutRef.current && !this.state.working && this.calloutRef.current.updateLayout(); } componentWillUnmount() { this.dismissed = true; } // Render render() { return this.props.uniqueAttribute || this.props.identity || this.props.displayName ? (React.createElement(IdentityCardContent, Object.assign({}, this.props, { dataProps: this.state.dataState, onClickEntity: this.onClickEntity, onDismissCallback: this.onDismissCallback, onShowContactCard: this.onShowContactCard, onShowOrganizationCard: this.onShowOrganizationCard, working: this.state.working, onHeaderClick: this.headerOnClickHandler, calloutRef: this.calloutRef, showUnknownUser: this.state.showUnknownUser }))) : null; } // Helper method to get initial data (which includes only direct manager) setupInitialData(uniqueAttribute) { const dataState = this.state.dataState; if (!dataState.identity && uniqueAttribute) { // Get identity first, then get connections in callback this.getIdentityByUniqueAttribute(uniqueAttribute); } else if (dataState.identity && !Utils.isCompleteIdentity(dataState.identity)) { // Seems to be cached, refetch identity by unique attribute (entityId) this.getIdentityByUniqueAttribute(dataState.identity); } else { if (Utils.isGroup(dataState.identity) && dataState.successors && dataState.successors.length > 0) { this.resolveIdentity(dataState, { successors: dataState.successors }); } else { this.resolveIdentity(dataState, this.props.onRequestConnectionInformation(dataState.identity)); } } } // Helper method to get identity information given an entity ID resolveIdentity(dataState, identity) { const identityAsEntity = identity; const identityAsPromiseLike = identity; if (identityAsPromiseLike && identityAsPromiseLike.then) { identityAsPromiseLike.then((connections) => { this.updateConnections(dataState, connections); }); } else if (identityAsEntity) { this.updateConnections(dataState, identityAsEntity); } } // Helper method to get identity information given an entity ID resolveIEntity(identity) { const identityAsEntity = identity; const identityAsPromiseLike = identity; if (identityAsPromiseLike && identityAsPromiseLike.then) { identityAsPromiseLike.then((connections) => { this.updateEntity(connections); }); } else { this.updateEntity(identityAsEntity); } } // Helper method to get identity information given an entity ID getIdentityByUniqueAttribute(identifier) { let uniqueAttribute; if (typeof identifier == "string") { uniqueAttribute = identifier; } else if (Utils.isGroup(identifier) || Utils.isAadUser(identifier) || Utils.isAdUser(identifier)) { uniqueAttribute = identifier.entityId; } else { uniqueAttribute = identifier.signInAddress ? identifier.signInAddress : ""; // VSD users (MSA accounts) } this.resolveIEntity(this.props.getEntityFromUniqueAttribute(uniqueAttribute)); } getEmail() { if (this.props.identity) { // This is for displaying a fall back card when identities search doesn't fetch any identity if ((Utils.isAadUser(this.props.identity) || Utils.isAdUser(this.props.identity)) && this.props.identity.mail) { return this.props.identity.mail; } else if (this.props.identity && this.props.identity.signInAddress) { return this.props.identity.signInAddress; } else if (this.props.identity && this.props.identity.mail) { return this.props.identity.mail; } else if (this.props.uniqueAttribute) { // Check if uniqueAttribute is an email const parts = this.props.uniqueAttribute.split("@"); if (parts.length === 2 && parts[0].length >= 1 && parts[1].length >= 3) { const domainParts = parts[1].split("."); if (domainParts.length > 1) { return this.props.uniqueAttribute; } } } } return ""; } }