azure-devops-ui
Version:
React components for building web UI in Azure DevOps
283 lines (282 loc) • 13.8 kB
JavaScript
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 "";
}
}