in src/FeatureTimeline/react/Components/FeatureTimelineGrid.tsx [170:527]
public render(): JSX.Element {
const {
uiState,
rawState,
settingsState
} = this.props;
if (!rawState || uiState === UIStatus.Loading) {
return (
<Spinner size={SpinnerSize.large} className="loading-indicator" label="Loading..." />
);
}
if (rawState.error) {
return (
<MessageBar
messageBarType={MessageBarType.error}
isMultiline={false}
>
{rawState.error}
</MessageBar>
);
}
if (uiState === UIStatus.NoTeamIterations) {
return (
<MessageBar
messageBarType={MessageBarType.error}
isMultiline={false}
>
{"The team does not have any iteration selected, please visit team admin page and select team iterations."}
</MessageBar>
);
}
if (uiState === UIStatus.NoWorkItems) {
return (<MessageBar
messageBarType={MessageBarType.info}
isMultiline={false}
>
{"No in-progress Features for the timeline."}
</MessageBar>);
}
const {
emptyHeaderRow,
iterationHeader,
iterationShadow,
workItems,
separators,
shadowForWorkItemId,
iterationDisplayOptions,
isSubGrid,
teamIterations
} = this.props.gridView;
const columnHeading = iterationHeader.map((iteration, index) => {
const style = getRowColumnStyle(iteration.dimension);
return (
<div className="columnheading" style={style}>
<IterationRenderer teamIterations={teamIterations} iteration={iteration.teamIteration} />
</div>
);
});
const shadows = iterationShadow.map((shadow, index) => {
return (
<IterationDropTarget
{...shadow}
isOverrideIterationInProgress={!!rawState.workItemOverrideIteration}
onOverrideIterationOver={this.props.dragHoverOverIteration.bind(this)}
changeIteration={this.props.changeIteration.bind(this)}
markInProgress={this.props.markInProgress.bind(this)}
>
</IterationDropTarget>
);
});
let workItemShadowCell = null;
if (shadowForWorkItemId) {
const workItem = workItems.filter(w => w.workItem.id === shadowForWorkItemId)[0];
workItemShadowCell = (
<WorkItemShadow dimension={workItem.dimension} twoRows={workItem.settingsState.showWorkItemDetails} />
);
}
const workItemCells = workItems.filter(w => w.workItem.id).map(w => {
return (
<DraggableWorkItemRenderer
id={w.workItem.id}
title={w.workItem.title}
color={w.workItem.color}
isRoot={w.workItem.isRoot}
assignedTo={w.workItem.workItem.fields["System.AssignedTo"]}
iterationDuration={w.workItem.iterationDuration}
dimension={w.dimension}
onClick={id => this.props.launchWorkItemForm(id)}
showDetails={id => this.props.showDetails(id)}
overrideIterationStart={payload => this.props.overrideIterationStart(payload)}
overrideIterationEnd={() => this.props.overrideIterationEnd()}
allowOverrideIteration={w.allowOverrideIteration}
isSubGrid={this.props.gridView.isSubGrid}
progressIndicator={w.progressIndicator}
crop={w.crop}
workItemStateColor={w.workItem.workItemStateColor}
settingsState={w.settingsState}
efforts={w.workItem.efforts}
childrernWithNoEfforts={w.workItem.childrenWithNoEfforts}
isComplete={w.workItem.isComplete}
successors={w.workItem.successors}
predecessors={w.workItem.predecessors}
highlightPredecessorIcon={w.workItem.highlightPredecessorIcon}
highlighteSuccessorIcon={w.workItem.highlighteSuccessorIcon}
onHighlightDependencies={() => {}}
onDismissDependencies={() => {}}
teamFieldName={"System.AreaPath"}
/>
);
});
const workItemSeparators = separators.map(d => {
return (
<ChildRowsSeparator {...d} />
);
});
const extraColumns = this.props.gridView.hideParents ? [] : ['300px'];
let min = '200px';
if (isSubGrid) {
min = '150px';
}
const gridStyle = getTemplateColumns(extraColumns, shadows.length, `minmax(${min}, 300px)`);
let childDialog = null;
if (this.props.childItems.length > 0) {
const props = { ...this.props, id: this.props.childItems[0] };
childDialog = <FeatureTimelineDialog {...props} />
}
let leftButton = <span className="non-button"></span>;
if (iterationDisplayOptions && iterationDisplayOptions.startIndex > 0) {
leftButton = (
<IconButton
className="button"
onClick={() => this.props.shiftDisplayIterationLeft(teamIterations.length)}
iconProps={
{
iconName: "ChevronLeftSmall"
}
}
>
</IconButton>
);
}
let rightButton = <span className="non-button"></span>;
if (iterationDisplayOptions && iterationDisplayOptions.endIndex < (iterationDisplayOptions.totalIterations - 1)) {
rightButton = (
<IconButton
className="button"
onClick={() => this.props.shiftDisplayIterationRight(teamIterations.length)}
iconProps={
{
iconName: "ChevronRightSmall"
}
}
>
</IconButton>
);
}
let displayOptions = null;
let commandHeading = [];
if (!isSubGrid && (iterationDisplayOptions || columnHeading.length > 3)) {
let displayIterationCount = 0;
if (iterationDisplayOptions) {
displayIterationCount = iterationDisplayOptions.count;
} else {
displayIterationCount = teamIterations.length;
}
displayOptions = (
<div className="iteration-options">
<div className="iteration-options-label">View Sprints: </div>
<InputNum
value={displayIterationCount}
min={1}
max={teamIterations.length}
step={1}
onChange={this._onViewChanged}
>
</InputNum>
</div>
);
if (emptyHeaderRow.length === 1) {
// Special case only one column
let rowColumnStyle = getRowColumnStyle(emptyHeaderRow[0]);
const commands = (
<div style={rowColumnStyle} className="single-column-commands">
<div className="command-left-section">
{leftButton}
</div>
<div className="command-right-section">
{rightButton}
</div>
</div>
);
commandHeading.push(commands);
} else {
// Add left button to first empty heading cell
let rowColumnStyle = getRowColumnStyle(emptyHeaderRow[0]);
const firstHeaderColumnCommand = (
<div style={rowColumnStyle} className="first-header-column-command">
{leftButton}
</div>
);
commandHeading.push(firstHeaderColumnCommand);
// Add display options and right button on last empty heading cell
rowColumnStyle = getRowColumnStyle(emptyHeaderRow[emptyHeaderRow.length - 1]);
const lastHeaderColumnCommand = (
<div style={rowColumnStyle} className="last-header-column-command">
{rightButton}
</div>
);
commandHeading.push(lastHeaderColumnCommand);
}
}
let progressTrackingCriteriaElement = null;
const {
showWorkItemDetails,
progressTrackingCriteria,
showClosedSinceDays
} = settingsState;
if (showWorkItemDetails) {
const selectedKey = progressTrackingCriteria === ProgressTrackingCriteria.ChildWorkItems ? "child" : "efforts";
progressTrackingCriteriaElement = (
<div className="progress-options">
<div className="progress-options-label">Track Progress Using: </div>
<ComboBox
className="progress-options-dropdown"
selectedKey={selectedKey}
allowFreeform={false}
autoComplete='off'
options={
[
{ key: 'child', text: 'Completed Stories' },
{ key: 'efforts', text: 'Completed Efforts' }
]
}
onChanged={this._onProgressTrackingCriteriaChanged}
>
</ComboBox>
</div>
);
}
const selectedKey = (showClosedSinceDays || '0').toString();
const showClosedSinceDaysElement = (
<div className="closed-since-options">
<div className="show-closed-since-label">Closed Features: </div>
<ComboBox
className="show-closed-since-dropdown"
selectedKey={selectedKey}
allowFreeform={false}
autoComplete='off'
options={
[
{ key: '0', text: 'Do not show' },
{ key: '30', text: 'Last 30 days' },
{ key: '60', text: 'Last 60 days' },
{ key: '90', text: 'Last 90 days' },
{ key: '120', text: 'Last 120 days' },
{ key: '180', text: 'Last 180 days' },
{ key: '365', text: 'Last 1 year' },
{ key: '9999', text: 'Forever (Slow)'}
]
}
onChanged={this._onShowClosedSinceChanged}
>
</ComboBox>
</div>
);
const commands = !isSubGrid && (
<div className="header-commands">
{displayOptions}
<Checkbox
className="plan-feature-checkbox"
label={"Plan Features"}
onChange={this._onShowPlanFeaturesChanged}
checked={this.props.planFeaturesState.show} />
<Checkbox
className="show-work-item-details-checkbox"
label={"Show Details"}
onChange={this._onShowWorkItemDetailsChanged}
checked={this.props.settingsState.showWorkItemDetails} />
{progressTrackingCriteriaElement}
{showClosedSinceDaysElement}
</div>
);
const grid = (
<div className="feature-timeline-main-container">
<div className="container" style={gridStyle}>
{commandHeading}
{columnHeading}
{shadows}
{workItemShadowCell}
{workItemCells}
{workItemSeparators}
{childDialog}
</div>
</div>
);
let contents = grid;
if (!isSubGrid && this.props.planFeaturesState.show) {
let paneWidth = this.props.planFeaturesState.paneWidth;
if (paneWidth > 25) {
paneWidth = 25;
}
contents = (
<SplitterLayout
customClassName={"timeline-splitter"}
secondaryInitialSize={paneWidth}
onSecondaryPaneSizeChange={this._onPaneWidthChanged}
percentage={true}
primaryMinSize="75"
>
{grid}
<ConnectedWorkItemsList />
</SplitterLayout>
);
}
return (
<div className="root-container">
{!this.props.settingsState.dismissedPortfolioPlansBanner &&
<PromotePortfolioPlansBanner onDismiss={this.props.dismissPortfolioPlansBanner}/>}
{commands}
{<div className="header-gap"></div>}
{contents}
</div>
);
}