-
Notifications
You must be signed in to change notification settings - Fork 85
/
Copy pathactionItem.tsx
212 lines (184 loc) · 8.73 KB
/
actionItem.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
import React from 'react';
import { DefaultButton, IButtonProps, PrimaryButton, BaseButton, Button } from 'office-ui-fabric-react/lib/Button';
import { DocumentCard, DocumentCardActions, DocumentCardTitle, DocumentCardType, DocumentCardPreview, IDocumentCardPreviewProps } from 'office-ui-fabric-react/lib/DocumentCard';
import { WorkItem, WorkItemType, WorkItemStateColor } from 'azure-devops-extension-api/WorkItemTracking/WorkItemTracking';
import { WorkItemTrackingServiceIds, IWorkItemFormNavigationService } from 'azure-devops-extension-api/WorkItemTracking';
import { workItemService } from '../dal/azureDevOpsWorkItemService';
import { itemDataService } from '../dal/itemDataService';
import { IFeedbackItemDocument } from '../interfaces/feedback';
import Dialog, { DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { withAITracking } from '@microsoft/applicationinsights-react-js';
import { reactPlugin } from '../utilities/telemetryClient';
import { SDKContext } from '../dal/azureDevOpsContextProvider';
export interface ActionItemProps extends IButtonProps {
feedbackItemId: string;
boardId: string;
actionItem: WorkItem;
nonHiddenWorkItemTypes: WorkItemType[];
allWorkItemTypes: WorkItemType[];
areActionIconsHidden: boolean;
shouldFocus: boolean;
onUpdateActionItem: (feedbackItemId: IFeedbackItemDocument) => void;
}
export interface ActionItemState {
isUnlinkWorkItemConfirmationDialogHidden: boolean;
linkedWorkItem: WorkItem;
workItemSearchTextboxHasErrors: boolean;
}
class ActionItem extends React.Component<ActionItemProps, ActionItemState> {
constructor(props: ActionItemProps) {
super(props);
this.state = {
isUnlinkWorkItemConfirmationDialogHidden: true,
linkedWorkItem: null as WorkItem,
workItemSearchTextboxHasErrors: false,
};
this.openWorkItemButton = null;
}
componentDidMount() {
if(this.props.shouldFocus && this.openWorkItemButton) {
this.openWorkItemButton.focus();
}
}
private openWorkItemButton: HTMLElement;
private readonly getWorkItemTypeIconProps = (workItemType: WorkItemType): IDocumentCardPreviewProps => {
return {
previewImages: [
{
previewIconContainerClass: 'work-item-type-icon-container',
width: 36,
previewIconProps: {
ariaLabel: `icon for work item type ${workItemType.name}`,
imageProps: {
src: workItemType.icon.url,
alt: `icon for work item type ${workItemType.name}`,
}
}
}
],
};
}
private readonly onActionItemClick = async (workItemId: number) => {
const { SDK } = React.useContext(SDKContext);
const workItemNavSvc = await SDK.getService<IWorkItemFormNavigationService>(WorkItemTrackingServiceIds.WorkItemFormNavigationService);
await workItemNavSvc.openWorkItem(workItemId);
// TODO: TASK 20104107 - When a work item is deleted, remove it as a link from all feedback items that it is linked to.
const updatedFeedbackItem = await itemDataService.removeAssociatedItemIfNotExistsInVsts(this.props.boardId, this.props.feedbackItemId, workItemId);
this.props.onUpdateActionItem(updatedFeedbackItem);
this.updateLinkedItem(workItemId);
if (this.openWorkItemButton) {
this.openWorkItemButton.focus();
}
}
private readonly onUnlinkWorkItemClick = async (workItemId: number) => {
const updatedFeedbackItem = await itemDataService.removeAssociatedActionItem(this.props.boardId, this.props.feedbackItemId, workItemId);
this.props.onUpdateActionItem(updatedFeedbackItem);
}
private readonly showUnlinkWorkItemConfirmationDialog = () => {
this.setState({
isUnlinkWorkItemConfirmationDialogHidden: false,
});
}
private readonly hideUnlinkWorkItemConfirmationDialog = () => {
this.setState({
isUnlinkWorkItemConfirmationDialogHidden: true,
});
}
private readonly onConfirmUnlinkWorkItem = async (workItemId: number) => {
this.onUnlinkWorkItemClick(workItemId);
this.hideUnlinkWorkItemConfirmationDialog();
}
private readonly updateLinkedItem = async (workItemId: number) => {
if (this.state.linkedWorkItem && this.state.linkedWorkItem.id === workItemId) {
const updatedLinkedWorkItem: WorkItem[] = await workItemService.getWorkItemsByIds([workItemId]);
if (updatedLinkedWorkItem) {
this.setState({
linkedWorkItem: updatedLinkedWorkItem[0],
});
}
}
}
private readonly handleKeyPressSelectorButton = (event: React.KeyboardEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button>) => {
if (event.key === 'Enter') {
this.showUnlinkWorkItem(event);
}
}
private readonly showUnlinkWorkItem = (event: React.KeyboardEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button> | React.MouseEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement, MouseEvent>) => {
event.preventDefault();
this.showUnlinkWorkItemConfirmationDialog();
event.stopPropagation();
}
private readonly showWorkItemForm = (event: React.KeyboardEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement, MouseEvent>) => {
event && event.stopPropagation();
this.onActionItemClick(this.props.actionItem.id);
}
public render() {
const workItemType: WorkItemType = this.props.allWorkItemTypes.find(wit => wit.name === this.props.actionItem.fields['System.WorkItemType']);
const iconProps: IDocumentCardPreviewProps = this.getWorkItemTypeIconProps(workItemType);
// Explicitly cast, since the returned contract contains states, but the interface defined does not
const workItemStates: WorkItemStateColor[] = workItemType.states ? workItemType.states : null;
const workItemState: WorkItemStateColor = workItemStates ? workItemStates.find(wisc => wisc.name === this.props.actionItem.fields['System.State']) : null;
const resolvedBorderRight: string = workItemState && (workItemState.category === 'Completed' || workItemState.category === 'Resolved') ? 'resolved-border-right' : '';
const systemTitle: string = this.props.actionItem.fields['System.Title'];
const title = systemTitle.length > 25 ? systemTitle.substring(0, 25) + "..." : systemTitle;
return (
<DocumentCard
key={`${this.props.actionItem.id}card`}
className={`related-task-sub-card ${resolvedBorderRight}`}
type={DocumentCardType.compact}>
<DocumentCardPreview key={`${this.props.actionItem.id}preview`} {...iconProps} />
<div
ref={(element: HTMLElement) => this.openWorkItemButton = element}
key={`${this.props.actionItem.id}details`}
className="ms-DocumentCard-details"
tabIndex={0}
role="button"
aria-label={`${this.props.actionItem.fields['System.WorkItemType']} ${this.props.actionItem.fields['System.Title']}, click to open work item`}
onKeyPress={(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === 'Enter') {
this.showWorkItemForm(e);
}
}}
onClick={this.showWorkItemForm}>
<DocumentCardTitle key={`${this.props.actionItem.id}title`} title={title} shouldTruncate={true} />
</div>
{!this.props.areActionIconsHidden &&
<DocumentCardActions
actions={[
{
iconProps: { iconName: 'RemoveLink' },
onClick: this.showUnlinkWorkItem,
onKeyPress: this.handleKeyPressSelectorButton,
title: 'Remove link to work item',
ariaLabel: 'Remove link to work item button'
}]
} />
}
{!this.state.isUnlinkWorkItemConfirmationDialogHidden &&
<Dialog
hidden={false}
onDismiss={this.hideUnlinkWorkItemConfirmationDialog}
dialogContentProps={{
type: DialogType.close,
title: 'Remove Work Item Link',
subText: `Are you sure you want to remove the link to work item '${this.props.actionItem.fields['System.Title']}'?`,
}}
modalProps={{
isBlocking: true,
containerClassName: 'retrospectives-unlink-work-item-confirmation-dialog',
className: 'retrospectives-dialog-modal',
}}>
<DialogFooter>
<PrimaryButton onClick={(e) => {
e && e.stopPropagation();
this.onConfirmUnlinkWorkItem(this.props.actionItem.id)
}} text="Remove" />
<DefaultButton onClick={this.hideUnlinkWorkItemConfirmationDialog} text="Cancel" />
</DialogFooter>
</Dialog>
}
</DocumentCard>
);
}
}
export default withAITracking(reactPlugin, ActionItem);